aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVinh Tran <vinhdaitran@google.com>2023-07-26 05:30:40 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2023-07-26 05:30:40 +0000
commit5c7cc0dce1331e9a2fcc166a80f4fab2c90ac590 (patch)
treeacdcc148cabfb6f8242a6c83e37625eeaea4e628
parent62c90a8ca6aa21d4e60cfe32b3784609d80480f6 (diff)
parent3e21f23d9400ba51f10e9b76016ff6d472829b4e (diff)
downloadbazelbuild-rules_python-android14-qpr2-s1-release.tar.gz
Original change: https://android-review.googlesource.com/c/platform/external/bazelbuild-rules_python/+/2673976 Change-Id: Ib33cbb9758fb3c0287c21cf502885fec2f072343 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r--.bazelci/presubmit.yml600
-rw-r--r--.bazelignore11
-rw-r--r--.bazelrc21
-rw-r--r--.bazelversion1
-rw-r--r--.bcr/config.yml18
-rw-r--r--.bcr/gazelle/metadata.template.json20
-rw-r--r--.bcr/gazelle/presubmit.yml27
-rw-r--r--.bcr/gazelle/source.template.json5
-rw-r--r--.bcr/metadata.template.json20
-rw-r--r--.bcr/presubmit.yml24
-rw-r--r--.bcr/source.template.json5
-rw-r--r--.ci/rules_python.json9
-rw-r--r--.git-blame-ignore-revs2
-rw-r--r--.gitattributes2
-rw-r--r--.github/CODEOWNERS15
-rw-r--r--.github/CODE_OF_CONDUCT.md76
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md76
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md45
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md11
-rwxr-xr-x.github/workflows/create_archive_and_notes.sh86
-rw-r--r--.github/workflows/release.yml46
-rw-r--r--.github/workflows/stale.yml73
-rw-r--r--.gitignore47
-rw-r--r--.pre-commit-config.yaml47
-rw-r--r--AUTHORS9
-rw-r--r--BUILD.bazel87
-rw-r--r--BZLMOD_SUPPORT.md61
-rw-r--r--CONTRIBUTING.md205
-rw-r--r--CONTRIBUTORS12
-rw-r--r--DEVELOPING.md39
-rw-r--r--LICENSE201
-rw-r--r--METADATA15
-rw-r--r--MODULE.bazel49
-rw-r--r--MODULE_LICENSE_APACHE20
-rw-r--r--OWNERS2
-rw-r--r--README.md346
-rw-r--r--WORKSPACE88
-rwxr-xr-xaddlicense.sh23
-rw-r--r--docs/BUILD.bazel187
-rw-r--r--docs/coverage.md58
-rwxr-xr-xdocs/packaging.md211
-rw-r--r--docs/pip.md273
-rw-r--r--docs/pip_repository.md242
-rw-r--r--docs/py_cc_toolchain.md32
-rw-r--r--docs/py_cc_toolchain_info.md27
-rwxr-xr-xdocs/python.md227
-rw-r--r--examples/BUILD.bazel72
-rw-r--r--examples/build_file_generation/.bazelrc5
-rw-r--r--examples/build_file_generation/.gitignore1
-rw-r--r--examples/build_file_generation/BUILD.bazel106
-rw-r--r--examples/build_file_generation/README.md22
-rw-r--r--examples/build_file_generation/WORKSPACE123
-rw-r--r--examples/build_file_generation/__init__.py26
-rw-r--r--examples/build_file_generation/__main__.py18
-rw-r--r--examples/build_file_generation/__test__.py28
-rw-r--r--examples/build_file_generation/gazelle_python.yaml118
-rw-r--r--examples/build_file_generation/random_number_generator/BUILD.bazel19
-rw-r--r--examples/build_file_generation/random_number_generator/__init__.py14
-rw-r--r--examples/build_file_generation/random_number_generator/__test__.py25
-rw-r--r--examples/build_file_generation/random_number_generator/generate_random_number.py19
-rw-r--r--examples/build_file_generation/requirements.in1
-rw-r--r--examples/build_file_generation/requirements_lock.txt78
-rw-r--r--examples/build_file_generation/requirements_windows.txt82
-rw-r--r--examples/bzlmod/.bazelignore1
-rw-r--r--examples/bzlmod/.bazelrc10
-rw-r--r--examples/bzlmod/.bazelversion1
-rw-r--r--examples/bzlmod/.gitignore1
-rw-r--r--examples/bzlmod/BUILD.bazel94
-rw-r--r--examples/bzlmod/MODULE.bazel130
-rw-r--r--examples/bzlmod/WORKSPACE2
-rw-r--r--examples/bzlmod/__main__.py20
-rw-r--r--examples/bzlmod/description.md10
-rw-r--r--examples/bzlmod/entry_point/BUILD.bazel20
-rw-r--r--examples/bzlmod/entry_point/test_entry_point.py43
-rw-r--r--examples/bzlmod/gazelle_python.yaml590
-rw-r--r--examples/bzlmod/lib.py19
-rw-r--r--examples/bzlmod/libs/my_lib/BUILD.bazel9
-rw-r--r--examples/bzlmod/libs/my_lib/__init__.py22
-rw-r--r--examples/bzlmod/other_module/MODULE.bazel34
-rw-r--r--examples/bzlmod/other_module/WORKSPACE0
-rw-r--r--examples/bzlmod/other_module/other_module/pkg/BUILD.bazel23
-rw-r--r--examples/bzlmod/other_module/other_module/pkg/data/data.txt1
-rw-r--r--examples/bzlmod/other_module/other_module/pkg/lib.py27
-rw-r--r--examples/bzlmod/requirements.in10
-rw-r--r--examples/bzlmod/requirements_lock_3_10.txt327
-rw-r--r--examples/bzlmod/requirements_lock_3_9.txt305
-rw-r--r--examples/bzlmod/requirements_windows_3_10.txt331
-rw-r--r--examples/bzlmod/requirements_windows_3_9.txt309
-rw-r--r--examples/bzlmod/runfiles/BUILD.bazel18
-rw-r--r--examples/bzlmod/runfiles/data/data.txt1
-rw-r--r--examples/bzlmod/runfiles/runfiles_test.py64
-rw-r--r--examples/bzlmod/test.py90
-rw-r--r--examples/bzlmod/tests/BUILD.bazel135
-rw-r--r--examples/bzlmod/tests/cross_version_test.py39
-rw-r--r--examples/bzlmod/tests/my_lib_test.py26
-rw-r--r--examples/bzlmod/tests/version.py17
-rw-r--r--examples/bzlmod/tests/version_test.py23
-rwxr-xr-xexamples/bzlmod/tests/version_test.sh24
-rw-r--r--examples/bzlmod/whl_mods/BUILD.bazel21
-rw-r--r--examples/bzlmod/whl_mods/appended_build_content.BUILD7
-rwxr-xr-xexamples/bzlmod/whl_mods/data/copy_executable.py18
-rw-r--r--examples/bzlmod/whl_mods/data/copy_file.txt1
-rw-r--r--examples/bzlmod/whl_mods/pip_whl_mods_test.py130
-rw-r--r--examples/bzlmod_build_file_generation/.bazelignore1
-rw-r--r--examples/bzlmod_build_file_generation/.bazelrc9
-rw-r--r--examples/bzlmod_build_file_generation/.bazelversion1
-rw-r--r--examples/bzlmod_build_file_generation/.gitignore1
-rw-r--r--examples/bzlmod_build_file_generation/BUILD.bazel98
-rw-r--r--examples/bzlmod_build_file_generation/MODULE.bazel87
-rw-r--r--examples/bzlmod_build_file_generation/README.md28
-rw-r--r--examples/bzlmod_build_file_generation/WORKSPACE2
-rw-r--r--examples/bzlmod_build_file_generation/__main__.py18
-rw-r--r--examples/bzlmod_build_file_generation/__test__.py33
-rw-r--r--examples/bzlmod_build_file_generation/gazelle_python.yaml591
-rw-r--r--examples/bzlmod_build_file_generation/lib.py19
-rw-r--r--examples/bzlmod_build_file_generation/other_module/MODULE.bazel5
-rw-r--r--examples/bzlmod_build_file_generation/other_module/WORKSPACE0
-rw-r--r--examples/bzlmod_build_file_generation/other_module/other_module/pkg/BUILD.bazel11
-rw-r--r--examples/bzlmod_build_file_generation/other_module/other_module/pkg/data/data.txt1
-rw-r--r--examples/bzlmod_build_file_generation/other_module/other_module/pkg/lib.py27
-rw-r--r--examples/bzlmod_build_file_generation/requirements.in6
-rw-r--r--examples/bzlmod_build_file_generation/requirements_lock.txt227
-rw-r--r--examples/bzlmod_build_file_generation/requirements_windows.txt231
-rw-r--r--examples/bzlmod_build_file_generation/runfiles/BUILD.bazel19
-rw-r--r--examples/bzlmod_build_file_generation/runfiles/data/data.txt1
-rw-r--r--examples/bzlmod_build_file_generation/runfiles/runfiles_test.py64
-rw-r--r--examples/multi_python_versions/.bazelrc7
-rw-r--r--examples/multi_python_versions/.gitignore1
-rw-r--r--examples/multi_python_versions/WORKSPACE55
-rw-r--r--examples/multi_python_versions/libs/my_lib/BUILD.bazel9
-rw-r--r--examples/multi_python_versions/libs/my_lib/__init__.py19
-rw-r--r--examples/multi_python_versions/requirements/BUILD.bazel32
-rw-r--r--examples/multi_python_versions/requirements/requirements.in1
-rw-r--r--examples/multi_python_versions/requirements/requirements_lock_3_10.txt56
-rw-r--r--examples/multi_python_versions/requirements/requirements_lock_3_11.txt56
-rw-r--r--examples/multi_python_versions/requirements/requirements_lock_3_8.txt56
-rw-r--r--examples/multi_python_versions/requirements/requirements_lock_3_9.txt56
-rw-r--r--examples/multi_python_versions/tests/BUILD.bazel184
-rw-r--r--examples/multi_python_versions/tests/cross_version_test.py39
-rw-r--r--examples/multi_python_versions/tests/my_lib_test.py24
-rw-r--r--examples/multi_python_versions/tests/version.py17
-rw-r--r--examples/multi_python_versions/tests/version_test.py23
-rwxr-xr-xexamples/multi_python_versions/tests/version_test.sh24
-rw-r--r--examples/pip_install/.bazelrc2
-rw-r--r--examples/pip_install/.gitignore4
-rw-r--r--examples/pip_install/BUILD.bazel111
-rw-r--r--examples/pip_install/README.md4
-rw-r--r--examples/pip_install/WORKSPACE96
-rw-r--r--examples/pip_install/main.py23
-rw-r--r--examples/pip_install/pip_install_test.py80
-rw-r--r--examples/pip_install/requirements.in4
-rw-r--r--examples/pip_install/requirements.txt110
-rw-r--r--examples/pip_install/requirements_windows.txt106
-rw-r--r--examples/pip_install/test.py26
-rw-r--r--examples/pip_parse/.bazelrc2
-rw-r--r--examples/pip_parse/.gitignore4
-rw-r--r--examples/pip_parse/BUILD.bazel81
-rw-r--r--examples/pip_parse/WORKSPACE52
-rw-r--r--examples/pip_parse/main.py19
-rw-r--r--examples/pip_parse/pip_parse_test.py77
-rw-r--r--examples/pip_parse/requirements.in3
-rw-r--r--examples/pip_parse/requirements_lock.txt95
-rw-r--r--examples/pip_parse/test.py26
-rw-r--r--examples/pip_parse_vendored/.bazelrc5
-rw-r--r--examples/pip_parse_vendored/.gitignore1
-rw-r--r--examples/pip_parse_vendored/BUILD.bazel52
-rw-r--r--examples/pip_parse_vendored/README.md31
-rw-r--r--examples/pip_parse_vendored/WORKSPACE35
-rw-r--r--examples/pip_parse_vendored/requirements.bzl55
-rw-r--r--examples/pip_parse_vendored/requirements.in1
-rw-r--r--examples/pip_parse_vendored/requirements.txt26
-rw-r--r--examples/pip_repository_annotations/.bazelrc2
-rw-r--r--examples/pip_repository_annotations/.gitignore1
-rw-r--r--examples/pip_repository_annotations/BUILD.bazel44
-rw-r--r--examples/pip_repository_annotations/WORKSPACE75
-rwxr-xr-xexamples/pip_repository_annotations/data/copy_executable.py18
-rw-r--r--examples/pip_repository_annotations/data/copy_file.txt1
-rw-r--r--examples/pip_repository_annotations/pip_repository_annotations_test.py129
-rw-r--r--examples/pip_repository_annotations/requirements.in6
-rw-r--r--examples/pip_repository_annotations/requirements.txt32
-rw-r--r--examples/py_proto_library/.bazelrc0
-rw-r--r--examples/py_proto_library/.gitignore4
-rw-r--r--examples/py_proto_library/BUILD.bazel22
-rw-r--r--examples/py_proto_library/MODULE.bazel23
-rw-r--r--examples/py_proto_library/WORKSPACE48
-rw-r--r--examples/py_proto_library/WORKSPACE.bzlmod0
-rw-r--r--examples/py_proto_library/pricetag.proto8
-rw-r--r--examples/py_proto_library/test.py17
-rw-r--r--examples/wheel/BUILD.bazel287
-rw-r--r--examples/wheel/NOTICE1
-rw-r--r--examples/wheel/README.md1
-rw-r--r--examples/wheel/lib/BUILD.bazel36
-rw-r--r--examples/wheel/lib/module_with_data.py17
-rw-r--r--examples/wheel/lib/simple_module.py17
-rw-r--r--examples/wheel/main.py30
-rw-r--r--examples/wheel/private/BUILD.bazel7
-rw-r--r--examples/wheel/private/directory_writer.py58
-rw-r--r--examples/wheel/private/wheel_utils.bzl73
-rw-r--r--examples/wheel/wheel_test.py414
-rw-r--r--gazelle/.bazelrc13
-rw-r--r--gazelle/.gitignore12
-rw-r--r--gazelle/BUILD.bazel35
-rw-r--r--gazelle/MODULE.bazel20
-rw-r--r--gazelle/README.md263
-rw-r--r--gazelle/WORKSPACE47
-rw-r--r--gazelle/def.bzl21
-rw-r--r--gazelle/deps.bzl261
-rw-r--r--gazelle/go.mod20
-rw-r--r--gazelle/go.sum94
-rw-r--r--gazelle/manifest/BUILD.bazel29
-rw-r--r--gazelle/manifest/defs.bzl181
-rw-r--r--gazelle/manifest/generate/BUILD.bazel28
-rw-r--r--gazelle/manifest/generate/generate.go196
-rw-r--r--gazelle/manifest/hasher/BUILD.bazel20
-rw-r--r--gazelle/manifest/hasher/main.go44
-rw-r--r--gazelle/manifest/manifest.go150
-rw-r--r--gazelle/manifest/manifest_test.go108
-rw-r--r--gazelle/manifest/test/BUILD.bazel9
-rw-r--r--gazelle/manifest/test/test.go78
-rw-r--r--gazelle/manifest/testdata/gazelle_python.yaml13
-rw-r--r--gazelle/manifest/testdata/requirements.txt3
-rw-r--r--gazelle/modules_mapping/BUILD.bazel13
-rw-r--r--gazelle/modules_mapping/def.bzl66
-rw-r--r--gazelle/modules_mapping/generator.py133
-rw-r--r--gazelle/python/BUILD.bazel80
-rw-r--r--gazelle/python/configure.go178
-rw-r--r--gazelle/python/fix.go27
-rw-r--r--gazelle/python/generate.go444
-rw-r--r--gazelle/python/kinds.go102
-rw-r--r--gazelle/python/language.go33
-rw-r--r--gazelle/python/lifecycle.go37
-rw-r--r--gazelle/python/parse.py106
-rw-r--r--gazelle/python/parser.go279
-rw-r--r--gazelle/python/python_test.go206
-rw-r--r--gazelle/python/resolve.go304
-rw-r--r--gazelle/python/std_modules.go114
-rw-r--r--gazelle/python/std_modules.py51
-rw-r--r--gazelle/python/target.go157
-rw-r--r--gazelle/python/testdata/README.md12
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/BUILD.in1
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/BUILD.out14
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/README.md7
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/WORKSPACE1
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/__init__.py24
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/bar/BUILD.in0
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/bar/BUILD.out8
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/bar/__init__.py17
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/baz/BUILD.in0
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/baz/BUILD.out8
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/baz/__init__.py17
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/foo/BUILD.in0
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/foo/BUILD.out8
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/foo/__init__.py17
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.in0
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.out8
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/somewhere/bar/__init__.py17
-rw-r--r--gazelle/python/testdata/dependency_resolution_order/test.yaml15
-rw-r--r--gazelle/python/testdata/disable_import_statements_validation/BUILD.in1
-rw-r--r--gazelle/python/testdata/disable_import_statements_validation/BUILD.out9
-rw-r--r--gazelle/python/testdata/disable_import_statements_validation/README.md3
-rw-r--r--gazelle/python/testdata/disable_import_statements_validation/WORKSPACE1
-rw-r--r--gazelle/python/testdata/disable_import_statements_validation/__init__.py17
-rw-r--r--gazelle/python/testdata/disable_import_statements_validation/test.yaml17
-rw-r--r--gazelle/python/testdata/dont_rename_target/BUILD.in5
-rw-r--r--gazelle/python/testdata/dont_rename_target/BUILD.out7
-rw-r--r--gazelle/python/testdata/dont_rename_target/README.md4
-rw-r--r--gazelle/python/testdata/dont_rename_target/WORKSPACE1
-rw-r--r--gazelle/python/testdata/dont_rename_target/__init__.py14
-rw-r--r--gazelle/python/testdata/dont_rename_target/test.yaml15
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/BUILD.in0
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/BUILD.out11
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/README.md4
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/WORKSPACE1
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/__init__.py15
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/rest_framework.py17
-rw-r--r--gazelle/python/testdata/file_name_matches_import_statement/test.yaml15
-rw-r--r--gazelle/python/testdata/first_party_dependencies/BUILD.in0
-rw-r--r--gazelle/python/testdata/first_party_dependencies/BUILD.out0
-rw-r--r--gazelle/python/testdata/first_party_dependencies/README.md11
-rw-r--r--gazelle/python/testdata/first_party_dependencies/WORKSPACE1
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/BUILD.in1
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/BUILD.out15
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/__main__.py26
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.in10
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.out11
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/bar/__init__.py19
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.in10
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.out11
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/bar/baz/__init__.py19
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.in11
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.out12
-rw-r--r--gazelle/python/testdata/first_party_dependencies/one/foo/__init__.py19
-rw-r--r--gazelle/python/testdata/first_party_dependencies/test.yaml15
-rw-r--r--gazelle/python/testdata/first_party_dependencies/three/BUILD.in1
-rw-r--r--gazelle/python/testdata/first_party_dependencies/three/BUILD.out14
-rw-r--r--gazelle/python/testdata/first_party_dependencies/three/__init__.py24
-rw-r--r--gazelle/python/testdata/first_party_dependencies/two/BUILD.in1
-rw-r--r--gazelle/python/testdata/first_party_dependencies/two/BUILD.out10
-rw-r--r--gazelle/python/testdata/first_party_dependencies/two/__init__.py20
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.in1
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.out25
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/README.md9
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/WORKSPACE1
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/__main__.py25
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/baz.py16
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/foo.py16
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.in0
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.out12
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/foo/__init__.py15
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/foo/bar.py21
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.in0
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.out11
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/one/__init__.py15
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/one/two.py16
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/test.yaml15
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.in1
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.out1
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.in12
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.out12
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/__init__.py15
-rw-r--r--gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/module1.py16
-rw-r--r--gazelle/python/testdata/from_imports/BUILD.in1
-rw-r--r--gazelle/python/testdata/from_imports/BUILD.out1
-rw-r--r--gazelle/python/testdata/from_imports/README.md7
-rw-r--r--gazelle/python/testdata/from_imports/WORKSPACE1
-rw-r--r--gazelle/python/testdata/from_imports/foo/BUILD.in1
-rw-r--r--gazelle/python/testdata/from_imports/foo/BUILD.out8
-rw-r--r--gazelle/python/testdata/from_imports/foo/__init__.py15
-rw-r--r--gazelle/python/testdata/from_imports/foo/bar/BUILD.in21
-rw-r--r--gazelle/python/testdata/from_imports/foo/bar/BUILD.out21
-rw-r--r--gazelle/python/testdata/from_imports/foo/bar/__init__.py15
-rw-r--r--gazelle/python/testdata/from_imports/foo/bar/baz.py15
-rw-r--r--gazelle/python/testdata/from_imports/gazelle_python.yaml19
-rw-r--r--gazelle/python/testdata/from_imports/import_from_init_py/BUILD.in0
-rw-r--r--gazelle/python/testdata/from_imports/import_from_init_py/BUILD.out9
-rw-r--r--gazelle/python/testdata/from_imports/import_from_init_py/__init__.py16
-rw-r--r--gazelle/python/testdata/from_imports/import_from_multiple/BUILD.in0
-rw-r--r--gazelle/python/testdata/from_imports/import_from_multiple/BUILD.out12
-rw-r--r--gazelle/python/testdata/from_imports/import_from_multiple/__init__.py16
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_file/BUILD.in0
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_file/BUILD.out9
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_file/__init__.py16
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_module/BUILD.in0
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_module/BUILD.out9
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_module/__init__.py16
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_var/BUILD.in0
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_var/BUILD.out9
-rw-r--r--gazelle/python/testdata/from_imports/import_nested_var/__init__.py16
-rw-r--r--gazelle/python/testdata/from_imports/import_top_level_var/BUILD.in0
-rw-r--r--gazelle/python/testdata/from_imports/import_top_level_var/BUILD.out9
-rw-r--r--gazelle/python/testdata/from_imports/import_top_level_var/__init__.py16
-rw-r--r--gazelle/python/testdata/from_imports/std_module/BUILD.in0
-rw-r--r--gazelle/python/testdata/from_imports/std_module/BUILD.out8
-rw-r--r--gazelle/python/testdata/from_imports/std_module/__init__.py17
-rw-r--r--gazelle/python/testdata/from_imports/test.yaml15
-rw-r--r--gazelle/python/testdata/generated_test_entrypoint/BUILD.in10
-rw-r--r--gazelle/python/testdata/generated_test_entrypoint/BUILD.out21
-rw-r--r--gazelle/python/testdata/generated_test_entrypoint/README.md4
-rw-r--r--gazelle/python/testdata/generated_test_entrypoint/WORKSPACE1
-rw-r--r--gazelle/python/testdata/generated_test_entrypoint/__init__.py17
-rw-r--r--gazelle/python/testdata/generated_test_entrypoint/foo.py16
-rw-r--r--gazelle/python/testdata/generated_test_entrypoint/test.yaml15
-rw-r--r--gazelle/python/testdata/ignored_invalid_imported_module/BUILD.in0
-rw-r--r--gazelle/python/testdata/ignored_invalid_imported_module/BUILD.out8
-rw-r--r--gazelle/python/testdata/ignored_invalid_imported_module/README.md3
-rw-r--r--gazelle/python/testdata/ignored_invalid_imported_module/WORKSPACE1
-rw-r--r--gazelle/python/testdata/ignored_invalid_imported_module/__init__.py36
-rw-r--r--gazelle/python/testdata/ignored_invalid_imported_module/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/ignored_invalid_imported_module/test.yaml17
-rw-r--r--gazelle/python/testdata/invalid_annotation/BUILD.in0
-rw-r--r--gazelle/python/testdata/invalid_annotation/BUILD.out0
-rw-r--r--gazelle/python/testdata/invalid_annotation/README.md2
-rw-r--r--gazelle/python/testdata/invalid_annotation/WORKSPACE1
-rw-r--r--gazelle/python/testdata/invalid_annotation/__init__.py15
-rw-r--r--gazelle/python/testdata/invalid_annotation/test.yaml19
-rw-r--r--gazelle/python/testdata/invalid_imported_module/BUILD.in0
-rw-r--r--gazelle/python/testdata/invalid_imported_module/BUILD.out0
-rw-r--r--gazelle/python/testdata/invalid_imported_module/README.md3
-rw-r--r--gazelle/python/testdata/invalid_imported_module/WORKSPACE1
-rw-r--r--gazelle/python/testdata/invalid_imported_module/__init__.py22
-rw-r--r--gazelle/python/testdata/invalid_imported_module/test.yaml22
-rw-r--r--gazelle/python/testdata/monorepo/BUILD.in1
-rw-r--r--gazelle/python/testdata/monorepo/BUILD.out1
-rw-r--r--gazelle/python/testdata/monorepo/README.md4
-rw-r--r--gazelle/python/testdata/monorepo/WORKSPACE1
-rw-r--r--gazelle/python/testdata/monorepo/a/BUILD.in1
-rw-r--r--gazelle/python/testdata/monorepo/a/BUILD.out1
-rw-r--r--gazelle/python/testdata/monorepo/a/README.md3
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/BUILD.in12
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/BUILD.out20
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/__init__.py26
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.in1
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.out10
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/_boundary/README.md5
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/_boundary/__init__.py14
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/bar/__init__.py23
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/bar/baz/__init__.py19
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/bar/baz/first_excluded.py15
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/bar/baz/hue.py15
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/bar/baz/second_excluded.py15
-rw-r--r--gazelle/python/testdata/monorepo/coarse_grained/foo/__init__.py19
-rw-r--r--gazelle/python/testdata/monorepo/gazelle_python.yaml19
-rw-r--r--gazelle/python/testdata/monorepo/one/BUILD.in2
-rw-r--r--gazelle/python/testdata/monorepo/one/BUILD.out17
-rw-r--r--gazelle/python/testdata/monorepo/one/__main__.py29
-rw-r--r--gazelle/python/testdata/monorepo/one/bar/BUILD.in10
-rw-r--r--gazelle/python/testdata/monorepo/one/bar/BUILD.out12
-rw-r--r--gazelle/python/testdata/monorepo/one/bar/__init__.py23
-rw-r--r--gazelle/python/testdata/monorepo/one/bar/baz/BUILD.in10
-rw-r--r--gazelle/python/testdata/monorepo/one/bar/baz/BUILD.out11
-rw-r--r--gazelle/python/testdata/monorepo/one/bar/baz/__init__.py19
-rw-r--r--gazelle/python/testdata/monorepo/one/foo/BUILD.in11
-rw-r--r--gazelle/python/testdata/monorepo/one/foo/BUILD.out12
-rw-r--r--gazelle/python/testdata/monorepo/one/foo/__init__.py19
-rw-r--r--gazelle/python/testdata/monorepo/one/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/monorepo/test.yaml15
-rw-r--r--gazelle/python/testdata/monorepo/three/BUILD.in5
-rw-r--r--gazelle/python/testdata/monorepo/three/BUILD.out21
-rw-r--r--gazelle/python/testdata/monorepo/three/__init__.py30
-rw-r--r--gazelle/python/testdata/monorepo/three/gazelle_python.yaml19
-rw-r--r--gazelle/python/testdata/monorepo/two/BUILD.in3
-rw-r--r--gazelle/python/testdata/monorepo/two/BUILD.out15
-rw-r--r--gazelle/python/testdata/monorepo/two/__init__.py22
-rw-r--r--gazelle/python/testdata/monorepo/two/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/BUILD.in0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/BUILD.out0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/__main__.py26
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.in0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.out0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/bar/__init__.py19
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.in0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.out0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/bar/baz/__init__.py19
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.in0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.out0
-rw-r--r--gazelle/python/testdata/monorepo/wont_generate/foo/__init__.py19
-rw-r--r--gazelle/python/testdata/multiple_tests/BUILD.in12
-rw-r--r--gazelle/python/testdata/multiple_tests/BUILD.out17
-rw-r--r--gazelle/python/testdata/multiple_tests/README.md3
-rw-r--r--gazelle/python/testdata/multiple_tests/WORKSPACE1
-rw-r--r--gazelle/python/testdata/multiple_tests/__init__.py0
-rw-r--r--gazelle/python/testdata/multiple_tests/bar_test.py24
-rw-r--r--gazelle/python/testdata/multiple_tests/foo_test.py24
-rw-r--r--gazelle/python/testdata/multiple_tests/test.yaml17
-rw-r--r--gazelle/python/testdata/naming_convention/BUILD.in3
-rw-r--r--gazelle/python/testdata/naming_convention/BUILD.out26
-rw-r--r--gazelle/python/testdata/naming_convention/README.md4
-rw-r--r--gazelle/python/testdata/naming_convention/WORKSPACE1
-rw-r--r--gazelle/python/testdata/naming_convention/__init__.py15
-rw-r--r--gazelle/python/testdata/naming_convention/__main__.py16
-rw-r--r--gazelle/python/testdata/naming_convention/__test__.py16
-rw-r--r--gazelle/python/testdata/naming_convention/dont_rename/BUILD.in7
-rw-r--r--gazelle/python/testdata/naming_convention/dont_rename/BUILD.out25
-rw-r--r--gazelle/python/testdata/naming_convention/dont_rename/__init__.py15
-rw-r--r--gazelle/python/testdata/naming_convention/dont_rename/__main__.py16
-rw-r--r--gazelle/python/testdata/naming_convention/dont_rename/__test__.py16
-rw-r--r--gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.in5
-rw-r--r--gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.out31
-rw-r--r--gazelle/python/testdata/naming_convention/resolve_conflict/__init__.py15
-rw-r--r--gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py16
-rw-r--r--gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py16
-rw-r--r--gazelle/python/testdata/naming_convention/test.yaml15
-rw-r--r--gazelle/python/testdata/naming_convention_binary_fail/BUILD.in1
-rw-r--r--gazelle/python/testdata/naming_convention_binary_fail/BUILD.out1
-rw-r--r--gazelle/python/testdata/naming_convention_binary_fail/README.md4
-rw-r--r--gazelle/python/testdata/naming_convention_binary_fail/WORKSPACE1
-rw-r--r--gazelle/python/testdata/naming_convention_binary_fail/__main__.py15
-rw-r--r--gazelle/python/testdata/naming_convention_binary_fail/test.yaml21
-rw-r--r--gazelle/python/testdata/naming_convention_library_fail/BUILD.in1
-rw-r--r--gazelle/python/testdata/naming_convention_library_fail/BUILD.out1
-rw-r--r--gazelle/python/testdata/naming_convention_library_fail/README.md4
-rw-r--r--gazelle/python/testdata/naming_convention_library_fail/WORKSPACE1
-rw-r--r--gazelle/python/testdata/naming_convention_library_fail/__init__.py15
-rw-r--r--gazelle/python/testdata/naming_convention_library_fail/test.yaml21
-rw-r--r--gazelle/python/testdata/naming_convention_test_fail/BUILD.in1
-rw-r--r--gazelle/python/testdata/naming_convention_test_fail/BUILD.out1
-rw-r--r--gazelle/python/testdata/naming_convention_test_fail/README.md4
-rw-r--r--gazelle/python/testdata/naming_convention_test_fail/WORKSPACE1
-rw-r--r--gazelle/python/testdata/naming_convention_test_fail/__test__.py15
-rw-r--r--gazelle/python/testdata/naming_convention_test_fail/test.yaml21
-rw-r--r--gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.in2
-rw-r--r--gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.out11
-rw-r--r--gazelle/python/testdata/python_ignore_dependencies_directive/README.md4
-rw-r--r--gazelle/python/testdata/python_ignore_dependencies_directive/WORKSPACE1
-rw-r--r--gazelle/python/testdata/python_ignore_dependencies_directive/__init__.py25
-rw-r--r--gazelle/python/testdata/python_ignore_dependencies_directive/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/python_ignore_dependencies_directive/test.yaml15
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/BUILD.in1
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/BUILD.out9
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/README.md3
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/WORKSPACE1
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/__init__.py15
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.in0
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.out8
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/bar/baz.py15
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/bar/some_other.py15
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.in1
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.out1
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/foo/baz.py15
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/setup.py15
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/some_other.py15
-rw-r--r--gazelle/python/testdata/python_ignore_files_directive/test.yaml15
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/BUILD.in0
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/BUILD.out22
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/README.md3
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/WORKSPACE0
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/__init__.py15
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/real_test.py18
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/test.yaml15
-rw-r--r--gazelle/python/testdata/python_target_with_test_in_name/test_reality.py16
-rw-r--r--gazelle/python/testdata/relative_imports/BUILD.in0
-rw-r--r--gazelle/python/testdata/relative_imports/BUILD.out21
-rw-r--r--gazelle/python/testdata/relative_imports/README.md4
-rw-r--r--gazelle/python/testdata/relative_imports/WORKSPACE1
-rw-r--r--gazelle/python/testdata/relative_imports/__main__.py19
-rw-r--r--gazelle/python/testdata/relative_imports/package1/module1.py19
-rw-r--r--gazelle/python/testdata/relative_imports/package1/module2.py16
-rw-r--r--gazelle/python/testdata/relative_imports/package2/BUILD.in0
-rw-r--r--gazelle/python/testdata/relative_imports/package2/BUILD.out13
-rw-r--r--gazelle/python/testdata/relative_imports/package2/__init__.py17
-rw-r--r--gazelle/python/testdata/relative_imports/package2/module3.py21
-rw-r--r--gazelle/python/testdata/relative_imports/package2/module4.py16
-rw-r--r--gazelle/python/testdata/relative_imports/package2/subpackage1/module5.py19
-rw-r--r--gazelle/python/testdata/relative_imports/test.yaml15
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/BUILD.in15
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/BUILD.out20
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/README.md3
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/WORKSPACE1
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/__init__.py17
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/__test__.py26
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/foo.py16
-rw-r--r--gazelle/python/testdata/respect_kind_mapping/test.yaml17
-rw-r--r--gazelle/python/testdata/sibling_imports/README.md3
-rw-r--r--gazelle/python/testdata/sibling_imports/WORKSPACE1
-rw-r--r--gazelle/python/testdata/sibling_imports/pkg/BUILD.in0
-rw-r--r--gazelle/python/testdata/sibling_imports/pkg/BUILD.out29
-rw-r--r--gazelle/python/testdata/sibling_imports/pkg/__init__.py0
-rw-r--r--gazelle/python/testdata/sibling_imports/pkg/a.py0
-rw-r--r--gazelle/python/testdata/sibling_imports/pkg/b.py2
-rw-r--r--gazelle/python/testdata/sibling_imports/pkg/test_util.py0
-rw-r--r--gazelle/python/testdata/sibling_imports/pkg/unit_test.py3
-rw-r--r--gazelle/python/testdata/sibling_imports/test.yaml1
-rw-r--r--gazelle/python/testdata/simple_binary/BUILD.in0
-rw-r--r--gazelle/python/testdata/simple_binary/BUILD.out8
-rw-r--r--gazelle/python/testdata/simple_binary/README.md3
-rw-r--r--gazelle/python/testdata/simple_binary/WORKSPACE1
-rw-r--r--gazelle/python/testdata/simple_binary/__main__.py15
-rw-r--r--gazelle/python/testdata/simple_binary/test.yaml15
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/BUILD.in18
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/BUILD.out27
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/README.md4
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/WORKSPACE1
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/__init__.py15
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/__main__.py16
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/bar.py15
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/foo.py15
-rw-r--r--gazelle/python/testdata/simple_binary_with_library/test.yaml15
-rw-r--r--gazelle/python/testdata/simple_library/BUILD.in0
-rw-r--r--gazelle/python/testdata/simple_library/BUILD.out7
-rw-r--r--gazelle/python/testdata/simple_library/README.md3
-rw-r--r--gazelle/python/testdata/simple_library/WORKSPACE1
-rw-r--r--gazelle/python/testdata/simple_library/__init__.py15
-rw-r--r--gazelle/python/testdata/simple_library/test.yaml15
-rw-r--r--gazelle/python/testdata/simple_library_without_init/BUILD.in0
-rw-r--r--gazelle/python/testdata/simple_library_without_init/BUILD.out0
-rw-r--r--gazelle/python/testdata/simple_library_without_init/README.md4
-rw-r--r--gazelle/python/testdata/simple_library_without_init/WORKSPACE1
-rw-r--r--gazelle/python/testdata/simple_library_without_init/foo/BUILD.in0
-rw-r--r--gazelle/python/testdata/simple_library_without_init/foo/BUILD.out8
-rw-r--r--gazelle/python/testdata/simple_library_without_init/foo/foo.py15
-rw-r--r--gazelle/python/testdata/simple_library_without_init/test.yaml15
-rw-r--r--gazelle/python/testdata/simple_test/BUILD.in6
-rw-r--r--gazelle/python/testdata/simple_test/BUILD.out17
-rw-r--r--gazelle/python/testdata/simple_test/README.md3
-rw-r--r--gazelle/python/testdata/simple_test/WORKSPACE1
-rw-r--r--gazelle/python/testdata/simple_test/__init__.py17
-rw-r--r--gazelle/python/testdata/simple_test/__test__.py26
-rw-r--r--gazelle/python/testdata/simple_test/foo.py16
-rw-r--r--gazelle/python/testdata/simple_test/test.yaml17
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/BUILD.in1
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/BUILD.out27
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/README.md4
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/WORKSPACE1
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/__init__.py17
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/__test__.py26
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.in1
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.out30
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/bar/__init__.py17
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/bar/__test__.py26
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/bar/bar.py17
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/bar/conftest.py13
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/conftest.py14
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/foo.py16
-rw-r--r--gazelle/python/testdata/simple_test_with_conftest/test.yaml17
-rw-r--r--gazelle/python/testdata/subdir_sources/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/BUILD.out12
-rw-r--r--gazelle/python/testdata/subdir_sources/README.md5
-rw-r--r--gazelle/python/testdata/subdir_sources/WORKSPACE1
-rw-r--r--gazelle/python/testdata/subdir_sources/__main__.py21
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/BUILD.out13
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/__init__.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/bar/bar.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/baz/baz.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/foo.py17
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.out8
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_build/python/my_module.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_build_bazel/BUILD.bazel.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_build_bazel/python/my_module.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.out11
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_init/__init__.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_init/python/my_module.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.out17
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py16
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_main/python/my_module.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.out16
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py16
-rw-r--r--gazelle/python/testdata/subdir_sources/foo/has_test/python/my_module.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/one/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/one/BUILD.out8
-rw-r--r--gazelle/python/testdata/subdir_sources/one/__init__.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/one/two/BUILD.in0
-rw-r--r--gazelle/python/testdata/subdir_sources/one/two/BUILD.out12
-rw-r--r--gazelle/python/testdata/subdir_sources/one/two/README.md2
-rw-r--r--gazelle/python/testdata/subdir_sources/one/two/__init__.py18
-rw-r--r--gazelle/python/testdata/subdir_sources/one/two/three.py15
-rw-r--r--gazelle/python/testdata/subdir_sources/test.yaml15
-rw-r--r--gazelle/python/testdata/with_nested_import_statements/BUILD.in0
-rw-r--r--gazelle/python/testdata/with_nested_import_statements/BUILD.out8
-rw-r--r--gazelle/python/testdata/with_nested_import_statements/README.md4
-rw-r--r--gazelle/python/testdata/with_nested_import_statements/WORKSPACE1
-rw-r--r--gazelle/python/testdata/with_nested_import_statements/__init__.py25
-rw-r--r--gazelle/python/testdata/with_nested_import_statements/gazelle_python.yaml18
-rw-r--r--gazelle/python/testdata/with_nested_import_statements/test.yaml15
-rw-r--r--gazelle/python/testdata/with_std_requirements/BUILD.in0
-rw-r--r--gazelle/python/testdata/with_std_requirements/BUILD.out7
-rw-r--r--gazelle/python/testdata/with_std_requirements/README.md4
-rw-r--r--gazelle/python/testdata/with_std_requirements/WORKSPACE1
-rw-r--r--gazelle/python/testdata/with_std_requirements/__init__.py19
-rw-r--r--gazelle/python/testdata/with_std_requirements/test.yaml15
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/BUILD.in0
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/BUILD.out24
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/README.md7
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/WORKSPACE1
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/__init__.py15
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/__main__.py19
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/bar.py25
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/foo.py25
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/gazelle_python.yaml21
-rw-r--r--gazelle/python/testdata/with_third_party_requirements/test.yaml15
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.in0
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.out25
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/README.md15
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/WORKSPACE1
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/__init__.py15
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/__main__.py20
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/bar.py20
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/gazelle_python.yaml1678
-rw-r--r--gazelle/python/testdata/with_third_party_requirements_from_imports/test.yaml15
-rw-r--r--gazelle/pythonconfig/BUILD.bazel28
-rw-r--r--gazelle/pythonconfig/pythonconfig.go361
-rw-r--r--gazelle/pythonconfig/pythonconfig_test.go28
-rw-r--r--gazelle/pythonconfig/types.go117
-rw-r--r--internal_deps.bzl184
-rw-r--r--internal_setup.bzl38
-rw-r--r--proposals/2018-10-25-selecting-between-python-2-and-3.md136
-rw-r--r--proposals/2018-11-08-customizing-the-python-stub-template.md47
-rw-r--r--proposals/2019-02-12-design-for-a-python-toolchain.md247
-rw-r--r--proposals/README.md11
-rw-r--r--python/BUILD.bazel241
-rw-r--r--python/cc/BUILD.bazel44
-rw-r--r--python/cc/py_cc_toolchain.bzl19
-rw-r--r--python/cc/py_cc_toolchain_info.bzl23
-rw-r--r--python/config_settings/BUILD.bazel12
-rw-r--r--python/config_settings/config_settings.bzl40
-rw-r--r--python/config_settings/private/BUILD.bazel0
-rw-r--r--python/config_settings/private/py_args.bzl42
-rw-r--r--python/config_settings/transition.bzl227
-rw-r--r--python/constraints/BUILD.bazel37
-rw-r--r--python/current_py_toolchain.bzl58
-rw-r--r--python/defs.bzl51
-rw-r--r--python/extensions/BUILD.bazel23
-rw-r--r--python/extensions/pip.bzl497
-rw-r--r--python/extensions/private/BUILD.bazel23
-rw-r--r--python/extensions/private/internal_deps.bzl23
-rw-r--r--python/extensions/private/pythons_hub.bzl142
-rw-r--r--python/extensions/python.bzl258
-rw-r--r--python/packaging.bzl182
-rw-r--r--python/pip.bzl367
-rw-r--r--python/pip_install/.gitignore135
-rw-r--r--python/pip_install/BUILD.bazel34
-rw-r--r--python/pip_install/pip_hub_repository_requirements_bzlmod.bzl.tmpl35
-rw-r--r--python/pip_install/pip_repository.bzl769
-rw-r--r--python/pip_install/pip_repository_requirements.bzl.tmpl54
-rw-r--r--python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl33
-rw-r--r--python/pip_install/private/BUILD.bazel24
-rw-r--r--python/pip_install/private/pip_install_utils.bzl132
-rw-r--r--python/pip_install/private/srcs.bzl19
-rw-r--r--python/pip_install/private/test/BUILD.bazel20
-rw-r--r--python/pip_install/private/test/requirements_parser_tests.bzl224
-rw-r--r--python/pip_install/repositories.bzl148
-rw-r--r--python/pip_install/requirements.bzl143
-rw-r--r--python/pip_install/requirements_parser.bzl133
-rw-r--r--python/pip_install/tools/dependency_resolver/BUILD.bazel19
-rw-r--r--python/pip_install/tools/dependency_resolver/__init__.py14
-rw-r--r--python/pip_install/tools/dependency_resolver/dependency_resolver.py221
-rw-r--r--python/pip_install/tools/lib/BUILD.bazel82
-rw-r--r--python/pip_install/tools/lib/__init__.py14
-rw-r--r--python/pip_install/tools/lib/annotation.py129
-rw-r--r--python/pip_install/tools/lib/annotations_test.py121
-rw-r--r--python/pip_install/tools/lib/annotations_test_helpers.bzl47
-rw-r--r--python/pip_install/tools/lib/arguments.py76
-rw-r--r--python/pip_install/tools/lib/arguments_test.py62
-rw-r--r--python/pip_install/tools/lib/bazel.py45
-rw-r--r--python/pip_install/tools/wheel_installer/BUILD.bazel66
-rw-r--r--python/pip_install/tools/wheel_installer/namespace_pkgs.py121
-rw-r--r--python/pip_install/tools/wheel_installer/namespace_pkgs_test.py192
-rw-r--r--python/pip_install/tools/wheel_installer/wheel.py111
-rw-r--r--python/pip_install/tools/wheel_installer/wheel_installer.py452
-rw-r--r--python/pip_install/tools/wheel_installer/wheel_installer_test.py108
-rw-r--r--python/private/BUILD.bazel106
-rw-r--r--python/private/bzlmod_enabled.bzl18
-rw-r--r--python/private/coverage.patch17
-rw-r--r--python/private/coverage_deps.bzl147
-rw-r--r--python/private/current_py_cc_headers.bzl41
-rw-r--r--python/private/normalize_name.bzl61
-rw-r--r--python/private/proto/BUILD.bazel46
-rw-r--r--python/private/proto/py_proto_library.bzl208
-rw-r--r--python/private/py_cc_toolchain_info.bzl43
-rw-r--r--python/private/py_cc_toolchain_macro.bzl31
-rw-r--r--python/private/py_cc_toolchain_rule.bzl57
-rw-r--r--python/private/py_package.bzl75
-rw-r--r--python/private/py_wheel.bzl442
-rw-r--r--python/private/reexports.bzl47
-rw-r--r--python/private/stamp.bzl87
-rw-r--r--python/private/toolchains_repo.bzl336
-rw-r--r--python/private/util.bzl85
-rw-r--r--python/private/version_label.bzl36
-rw-r--r--python/proto.bzl21
-rw-r--r--python/py_binary.bzl31
-rw-r--r--python/py_cc_link_params_info.bzl3
-rw-r--r--python/py_import.bzl67
-rw-r--r--python/py_info.bzl19
-rw-r--r--python/py_library.bzl29
-rw-r--r--python/py_runtime.bzl29
-rw-r--r--python/py_runtime_info.bzl19
-rw-r--r--python/py_runtime_pair.bzl87
-rw-r--r--python/py_test.bzl31
-rw-r--r--python/python.bzl67
-rw-r--r--python/repositories.bzl637
-rw-r--r--python/runfiles/BUILD.bazel58
-rw-r--r--python/runfiles/README.rst56
-rw-r--r--python/runfiles/__init__.py15
-rw-r--r--python/runfiles/runfiles.py368
-rw-r--r--python/tests/toolchains/BUILD.bazel20
-rw-r--r--python/tests/toolchains/defs.bzl176
-rw-r--r--python/tests/toolchains/run_acceptance_test.py.tmpl70
-rw-r--r--python/tests/toolchains/versions_test.bzl51
-rw-r--r--python/tests/toolchains/workspace_template/BUILD.bazel5
-rw-r--r--python/tests/toolchains/workspace_template/BUILD.bazel.tmpl9
-rw-r--r--python/tests/toolchains/workspace_template/README.md4
-rw-r--r--python/tests/toolchains/workspace_template/WORKSPACE.tmpl39
-rw-r--r--python/tests/toolchains/workspace_template/python_version_test.py26
-rw-r--r--python/versions.bzl406
-rw-r--r--renovate.json5
-rw-r--r--tests/BUILD.bazel34
-rw-r--r--tests/cc/BUILD.bazel106
-rw-r--r--tests/cc/current_py_cc_headers/BUILD.bazel17
-rw-r--r--tests/cc/current_py_cc_headers/current_py_cc_headers_tests.bzl69
-rw-r--r--tests/cc/fake_cc_toolchain_config.bzl37
-rw-r--r--tests/cc/py_cc_toolchain/BUILD.bazel3
-rw-r--r--tests/cc/py_cc_toolchain/py_cc_toolchain_tests.bzl83
-rw-r--r--tests/cc_info_subject.bzl128
-rw-r--r--tests/compile_pip_requirements/.bazelrc5
-rw-r--r--tests/compile_pip_requirements/.gitignore1
-rw-r--r--tests/compile_pip_requirements/BUILD.bazel79
-rw-r--r--tests/compile_pip_requirements/README.md3
-rw-r--r--tests/compile_pip_requirements/WORKSPACE17
-rw-r--r--tests/compile_pip_requirements/requirements.txt1
-rw-r--r--tests/compile_pip_requirements/requirements_lock.txt14
-rw-r--r--tests/compile_pip_requirements/requirements_lock_darwin.txt10
-rw-r--r--tests/compile_pip_requirements/requirements_lock_linux.txt10
-rw-r--r--tests/compile_pip_requirements/requirements_lock_windows.txt10
-rw-r--r--tests/compile_pip_requirements/requirements_nohashes_lock.txt10
-rw-r--r--tests/compile_pip_requirements_test_from_external_workspace/.bazelrc1
-rw-r--r--tests/compile_pip_requirements_test_from_external_workspace/.gitignore1
-rw-r--r--tests/compile_pip_requirements_test_from_external_workspace/BUILD.bazel0
-rw-r--r--tests/compile_pip_requirements_test_from_external_workspace/README.md3
-rw-r--r--tests/compile_pip_requirements_test_from_external_workspace/WORKSPACE36
-rw-r--r--tests/config_settings/transition/BUILD.bazel3
-rw-r--r--tests/config_settings/transition/py_args_tests.bzl68
-rw-r--r--tests/default_info_subject.bzl34
-rw-r--r--tests/ignore_root_user_error/.bazelrc5
-rw-r--r--tests/ignore_root_user_error/.gitignore1
-rw-r--r--tests/ignore_root_user_error/BUILD.bazel7
-rw-r--r--tests/ignore_root_user_error/README.md2
-rw-r--r--tests/ignore_root_user_error/WORKSPACE27
-rw-r--r--tests/ignore_root_user_error/foo_test.py13
-rw-r--r--tests/load_from_macro/BUILD.bazel34
-rw-r--r--tests/load_from_macro/foo.py13
-rw-r--r--tests/load_from_macro/tags.bzl18
-rw-r--r--tests/pip_hub_repository/normalize_name/BUILD.bazel3
-rw-r--r--tests/pip_hub_repository/normalize_name/normalize_name_tests.bzl50
-rw-r--r--tests/pip_repository_entry_points/.bazelrc7
-rw-r--r--tests/pip_repository_entry_points/.gitignore4
-rw-r--r--tests/pip_repository_entry_points/BUILD.bazel55
-rw-r--r--tests/pip_repository_entry_points/WORKSPACE44
-rw-r--r--tests/pip_repository_entry_points/pip_repository_entry_points_test.py83
-rw-r--r--tests/pip_repository_entry_points/requirements.in5
-rw-r--r--tests/pip_repository_entry_points/requirements.txt215
-rw-r--r--tests/pip_repository_entry_points/requirements_windows.txt219
-rw-r--r--tests/py_cc_toolchain_info_subject.bzl47
-rw-r--r--tests/py_wheel/BUILD.bazel18
-rw-r--r--tests/py_wheel/py_wheel_tests.bzl99
-rw-r--r--tests/runfiles/BUILD.bazel7
-rw-r--r--tests/runfiles/runfiles_test.py558
-rwxr-xr-xtests/runfiles/runfiles_wheel_integration_test.sh24
-rw-r--r--tests/version_label/BUILD.bazel17
-rw-r--r--tests/version_label/version_label_test.bzl52
-rw-r--r--tools/BUILD.bazel33
-rw-r--r--tools/bazel_integration_test/BUILD.bazel1
-rw-r--r--tools/bazel_integration_test/bazel_integration_test.bzl134
-rw-r--r--tools/bazel_integration_test/test_runner.py111
-rwxr-xr-xtools/bazel_integration_test/update_deleted_packages.sh39
-rw-r--r--tools/build_defs/python/BUILD.bazel13
-rw-r--r--tools/build_defs/python/tests/BUILD.bazel27
-rw-r--r--tools/build_defs/python/tests/base_tests.bzl124
-rw-r--r--tools/build_defs/python/tests/py_binary/BUILD.bazel17
-rw-r--r--tools/build_defs/python/tests/py_binary/py_binary_tests.bzl28
-rw-r--r--tools/build_defs/python/tests/py_executable_base_tests.bzl272
-rw-r--r--tools/build_defs/python/tests/py_info_subject.bzl95
-rw-r--r--tools/build_defs/python/tests/py_library/BUILD.bazel18
-rw-r--r--tools/build_defs/python/tests/py_library/py_library_tests.bzl148
-rw-r--r--tools/build_defs/python/tests/py_test/BUILD.bazel18
-rw-r--r--tools/build_defs/python/tests/py_test/py_test_tests.bzl107
-rw-r--r--tools/build_defs/python/tests/py_wheel/BUILD.bazel18
-rw-r--r--tools/build_defs/python/tests/py_wheel/py_wheel_tests.bzl39
-rw-r--r--tools/build_defs/python/tests/util.bzl78
-rw-r--r--tools/publish/BUILD.bazel7
-rw-r--r--tools/publish/README.md6
-rw-r--r--tools/publish/requirements.in1
-rw-r--r--tools/publish/requirements.txt297
-rw-r--r--tools/publish/requirements_darwin.txt192
-rw-r--r--tools/publish/requirements_windows.txt196
-rwxr-xr-xtools/update_coverage_deps.py248
-rw-r--r--tools/wheelmaker.py436
-rw-r--r--version.bzl39
854 files changed, 38926 insertions, 0 deletions
diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml
new file mode 100644
index 0000000..1da4d9f
--- /dev/null
+++ b/.bazelci/presubmit.yml
@@ -0,0 +1,600 @@
+# 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.
+
+---
+buildifier:
+ # keep these arguments in sync with .pre-commit-config.yaml
+ # Use a specific version to avoid skew issues when new versions are released.
+ version: 6.1.0
+ warnings: "all"
+.minimum_supported_version: &minimum_supported_version
+ # For testing minimum supported version.
+ # NOTE: Keep in sync with //:version.bzl
+ bazel: 5.4.0
+.minimum_supported_bzlmod_version: &minimum_supported_bzlmod_version
+ bazel: 6.0.0 # test minimum supported version of bazel for bzlmod tests
+.reusable_config: &reusable_config
+ build_targets:
+ - "--"
+ - "..."
+ # As a regression test for #225, check that wheel targets still build when
+ # their package path is qualified with the repo name.
+ - "@rules_python//examples/wheel/..."
+ build_flags:
+ - "--keep_going"
+ test_targets:
+ - "--"
+ - "..."
+ test_flags:
+ - "--test_tag_filters=-integration-test"
+.common_bzlmod_flags: &common_bzlmod_flags
+ test_flags:
+ - "--experimental_enable_bzlmod"
+ build_flags:
+ - "--experimental_enable_bzlmod"
+.reusable_build_test_all: &reusable_build_test_all
+ build_targets: ["..."]
+ test_targets: ["..."]
+.coverage_targets_example_bzlmod: &coverage_targets_example_bzlmod
+ coverage_targets: ["//:test"]
+.coverage_targets_example_bzlmod_build_file_generation: &coverage_targets_example_bzlmod_build_file_generation
+ coverage_targets: ["//:bzlmod_build_file_generation_test"]
+.coverage_targets_example_multi_python: &coverage_targets_example_multi_python
+ coverage_targets:
+ - //tests:my_lib_3_10_test
+ - //tests:my_lib_3_11_test
+ - //tests:my_lib_3_8_test
+ - //tests:my_lib_3_9_test
+ - //tests:my_lib_default_test
+ - //tests:version_3_10_test
+ - //tests:version_3_11_test
+ - //tests:version_3_8_test
+ - //tests:version_3_9_test
+ - //tests:version_default_test
+tasks:
+ gazelle_extension_min:
+ <<: *minimum_supported_version
+ name: Test the Gazelle extension using minimum supported Bazel version
+ platform: ubuntu2004
+ build_targets: ["//..."]
+ test_targets: ["//..."]
+ working_directory: gazelle
+ gazelle_extension:
+ name: Test the Gazelle extension
+ platform: ubuntu2004
+ build_targets: ["//..."]
+ test_targets: ["//..."]
+ working_directory: gazelle
+ gazelle_extension_bzlmod:
+ <<: *common_bzlmod_flags
+ name: Test the Gazelle extension under bzlmod
+ platform: ubuntu2004
+ build_targets: ["//..."]
+ test_targets: ["//..."]
+ working_directory: gazelle
+
+ ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_config
+ name: Default test on Ubuntu using minimum supported Bazel version
+ platform: ubuntu2004
+ ubuntu:
+ <<: *reusable_config
+ name: Default test on Ubuntu
+ platform: ubuntu2004
+ debian:
+ <<: *reusable_config
+ name: Default test on Debian
+ platform: debian11
+ macos:
+ <<: *reusable_config
+ name: Default test on macOS
+ platform: macos
+ windows:
+ <<: *reusable_config
+ name: Default test on Windows
+ platform: windows
+ test_flags:
+ - "--test_tag_filters=-integration-test,-fix-windows"
+
+ rbe_min:
+ <<: *minimum_supported_version
+ <<: *reusable_config
+ name: Test on RBE using minimum supported Bazel version
+ platform: rbe_ubuntu1604
+ build_flags:
+ # BazelCI sets --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1,
+ # which prevents cc toolchain autodetection from working correctly
+ # on Bazel 5.4 and earlier. To workaround this, manually specify the
+ # build kite cc toolchain.
+ - "--extra_toolchains=@buildkite_config//config:cc-toolchain"
+ test_flags:
+ - "--test_tag_filters=-integration-test,-acceptance-test"
+ # BazelCI sets --action_env=BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1,
+ # which prevents cc toolchain autodetection from working correctly
+ # on Bazel 5.4 and earlier. To workaround this, manually specify the
+ # build kite cc toolchain.
+ - "--extra_toolchains=@buildkite_config//config:cc-toolchain"
+ rbe:
+ <<: *reusable_config
+ name: Test on RBE
+ platform: rbe_ubuntu1604
+ test_flags:
+ - "--test_tag_filters=-integration-test,-acceptance-test"
+
+ integration_test_build_file_generation_ubuntu_minimum_supported:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: build_file_generation integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/build_file_generation
+ platform: ubuntu2004
+ integration_test_build_file_generation_ubuntu:
+ <<: *reusable_build_test_all
+ name: build_file_generation integration tests on Ubuntu
+ working_directory: examples/build_file_generation
+ platform: ubuntu2004
+ integration_test_build_file_generation_debian:
+ <<: *reusable_build_test_all
+ name: build_file_generation integration tests on Debian
+ working_directory: examples/build_file_generation
+ platform: debian11
+ integration_test_build_file_generation_macos:
+ <<: *reusable_build_test_all
+ name: build_file_generation integration tests on macOS
+ working_directory: examples/build_file_generation
+ platform: macos
+ integration_test_build_file_generation_windows:
+ <<: *reusable_build_test_all
+ name: build_file_generation integration tests on Windows
+ working_directory: examples/build_file_generation
+ platform: windows
+
+ integration_test_bzlmod_ubuntu_min:
+ <<: *minimum_supported_bzlmod_version
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod
+ name: bzlmod integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/bzlmod
+ platform: ubuntu2004
+ integration_test_bzlmod_ubuntu:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod
+ name: bzlmod integration tests on Ubuntu
+ working_directory: examples/bzlmod
+ platform: ubuntu2004
+ integration_test_bzlmod_debian:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod
+ name: bzlmod integration tests on Debian
+ working_directory: examples/bzlmod
+ platform: debian11
+ integration_test_bzlmod_macos:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod
+ name: bzlmod integration tests on macOS
+ working_directory: examples/bzlmod
+ platform: macos
+ integration_test_bzlmod_windows:
+ <<: *reusable_build_test_all
+ # coverage is not supported on Windows
+ name: bzlmod integration tests on Windows
+ working_directory: examples/bzlmod
+ platform: windows
+
+ integration_test_bzlmod_generate_build_file_generation_ubuntu_min:
+ <<: *minimum_supported_bzlmod_version
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod_build_file_generation
+ name: example bzlmod build file min bazel version integration test
+ working_directory: examples/bzlmod_build_file_generation
+ platform: ubuntu2004
+ integration_test_bzlmod_generation_build_files_ubuntu:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod_build_file_generation
+ name: example bzlmod build file integration test
+ working_directory: examples/bzlmod_build_file_generation
+ platform: ubuntu2004
+ integration_test_bzlmod_generation_build_files_ubuntu_run:
+ <<: *reusable_build_test_all
+ name: example bzlmod build file running gazelle and pip integration test
+ working_directory: examples/bzlmod_build_file_generation
+ platform: ubuntu2004
+ shell_commands:
+ - "bazel run //:gazelle_python_manifest.update"
+ - "bazel run //:gazelle -- update"
+ integration_test_bzlmod_build_file_generation_debian:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod_build_file_generation
+ name: example bzlmod build file integration test
+ working_directory: examples/bzlmod_build_file_generation
+ platform: debian11
+ integration_test_bzlmod_build_file_generation_macos:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_bzlmod_build_file_generation
+ name: example bzlmod build file integration test
+ working_directory: examples/bzlmod_build_file_generation
+ platform: macos
+ integration_test_bzlmod_build_file_generation_windows:
+ <<: *reusable_build_test_all
+ # coverage is not supported on Windows
+ name: example bzlmod build file integration test
+ working_directory: examples/bzlmod_build_file_generation
+ platform: windows
+
+ integration_test_multi_python_versions_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: multi_python_versions integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/multi_python_versions
+ platform: ubuntu2004
+ integration_test_multi_python_versions_ubuntu:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_multi_python
+ name: multi_python_versions integration tests on Ubuntu
+ working_directory: examples/multi_python_versions
+ platform: ubuntu2004
+ integration_test_multi_python_versions_debian:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_multi_python
+ name: multi_python_versions integration tests on Debian
+ working_directory: examples/multi_python_versions
+ platform: debian11
+ integration_test_multi_python_versions_macos:
+ <<: *reusable_build_test_all
+ <<: *coverage_targets_example_multi_python
+ name: multi_python_versions integration tests on macOS
+ working_directory: examples/multi_python_versions
+ platform: macos
+ integration_test_multi_python_versions_windows:
+ <<: *reusable_build_test_all
+ # coverage is not supported on Windows
+ name: multi_python_versions integration tests on Windows
+ working_directory: examples/multi_python_versions
+ platform: windows
+
+ integration_test_pip_install_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: pip_install integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/pip_install
+ platform: ubuntu2004
+ integration_test_pip_install_ubuntu:
+ <<: *reusable_build_test_all
+ name: pip_install integration tests on Ubuntu
+ working_directory: examples/pip_install
+ platform: ubuntu2004
+ integration_test_pip_install_debian:
+ <<: *reusable_build_test_all
+ name: pip_install integration tests on Debian
+ working_directory: examples/pip_install
+ platform: debian11
+ integration_test_pip_install_macos:
+ <<: *reusable_build_test_all
+ name: pip_install integration tests on macOS
+ working_directory: examples/pip_install
+ platform: macos
+ integration_test_pip_install_windows:
+ <<: *reusable_build_test_all
+ name: pip_install integration tests on Windows
+ working_directory: examples/pip_install
+ platform: windows
+
+ integration_test_pip_parse_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: pip_parse integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/pip_parse
+ platform: ubuntu2004
+ integration_test_pip_parse_ubuntu:
+ <<: *reusable_build_test_all
+ name: pip_parse integration tests on Ubuntu
+ working_directory: examples/pip_parse
+ platform: ubuntu2004
+ integration_test_pip_parse_debian:
+ <<: *reusable_build_test_all
+ name: pip_parse integration tests on Debian
+ working_directory: examples/pip_parse
+ platform: debian11
+ integration_test_pip_parse_macos:
+ <<: *reusable_build_test_all
+ name: pip_parse integration tests on macOS
+ working_directory: examples/pip_parse
+ platform: macos
+ integration_test_pip_parse_windows:
+ <<: *reusable_build_test_all
+ name: pip_parse integration tests on Windows
+ working_directory: examples/pip_parse
+ platform: windows
+
+ integration_test_pip_parse_vendored_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: pip_parse_vendored integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/pip_parse_vendored
+ platform: ubuntu2004
+ integration_test_pip_parse_vendored_ubuntu:
+ <<: *reusable_build_test_all
+ name: pip_parse_vendored integration tests on Ubuntu
+ working_directory: examples/pip_parse_vendored
+ platform: ubuntu2004
+ integration_test_pip_parse_vendored_debian:
+ <<: *reusable_build_test_all
+ name: pip_parse_vendored integration tests on Debian
+ working_directory: examples/pip_parse_vendored
+ platform: debian11
+ integration_test_pip_parse_vendored_macos:
+ <<: *reusable_build_test_all
+ name: pip_parse_vendored integration tests on macOS
+ working_directory: examples/pip_parse_vendored
+ platform: macos
+ # We don't run pip_parse_vendored under Windows as the file checked in is
+ # generated from a repository rule containing OS-specific rendered paths.
+
+ integration_test_py_proto_library_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: py_proto_library integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/py_proto_library
+ platform: ubuntu2004
+ integration_test_py_proto_library_ubuntu:
+ <<: *reusable_build_test_all
+ name: py_proto_library integration tests on Ubuntu
+ working_directory: examples/py_proto_library
+ platform: ubuntu2004
+ integration_test_py_proto_library_debian:
+ <<: *reusable_build_test_all
+ name: py_proto_library integration tests on Debian
+ working_directory: examples/py_proto_library
+ platform: debian11
+ integration_test_py_proto_library_macos:
+ <<: *reusable_build_test_all
+ name: py_proto_library integration tests on macOS
+ working_directory: examples/py_proto_library
+ platform: macos
+ integration_test_py_proto_library_windows:
+ <<: *reusable_build_test_all
+ name: py_proto_library integration tests on Windows
+ working_directory: examples/py_proto_library
+ platform: windows
+
+ # Check the same using bzlmod as well
+ integration_test_py_proto_library_bzlmod_ubuntu_min:
+ <<: *minimum_supported_bzlmod_version
+ <<: *common_bzlmod_flags
+ <<: *reusable_build_test_all
+ name: py_proto_library bzlmod integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/py_proto_library
+ platform: ubuntu2004
+ integration_test_py_proto_library_bzlmod_ubuntu:
+ <<: *reusable_build_test_all
+ <<: *common_bzlmod_flags
+ name: py_proto_library bzlmod integration tests on Ubuntu
+ working_directory: examples/py_proto_library
+ platform: ubuntu2004
+ integration_test_py_proto_library_bzlmod_debian:
+ <<: *reusable_build_test_all
+ <<: *common_bzlmod_flags
+ name: py_proto_library bzlmod integration tests on Debian
+ working_directory: examples/py_proto_library
+ platform: debian11
+ integration_test_py_proto_library_bzlmod_macos:
+ <<: *reusable_build_test_all
+ <<: *common_bzlmod_flags
+ name: py_proto_library bzlmod integration tests on macOS
+ working_directory: examples/py_proto_library
+ platform: macos
+ integration_test_py_proto_library_bzlmod_windows:
+ <<: *reusable_build_test_all
+ <<: *common_bzlmod_flags
+ name: py_proto_library bzlmod integration tests on Windows
+ working_directory: examples/py_proto_library
+ platform: windows
+
+ integration_test_pip_repository_annotations_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: pip_repository_annotations integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: examples/pip_repository_annotations
+ platform: ubuntu2004
+ integration_test_pip_repository_annotations_ubuntu:
+ <<: *reusable_build_test_all
+ name: pip_repository_annotations integration tests on Ubuntu
+ working_directory: examples/pip_repository_annotations
+ platform: ubuntu2004
+ integration_test_pip_repository_annotations_debian:
+ <<: *reusable_build_test_all
+ name: pip_repository_annotations integration tests on Debian
+ working_directory: examples/pip_repository_annotations
+ platform: debian11
+ integration_test_pip_repository_annotations_macos:
+ <<: *reusable_build_test_all
+ name: pip_repository_annotations integration tests on macOS
+ working_directory: examples/pip_repository_annotations
+ platform: macos
+ integration_test_pip_repository_annotations_windows:
+ <<: *reusable_build_test_all
+ name: pip_repository_annotations integration tests on Windows
+ working_directory: examples/pip_repository_annotations
+ platform: windows
+
+ integration_test_compile_pip_requirements_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: compile_pip_requirements integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: tests/compile_pip_requirements
+ platform: ubuntu2004
+ integration_test_compile_pip_requirements_ubuntu:
+ <<: *reusable_build_test_all
+ name: compile_pip_requirements integration tests on Ubuntu
+ working_directory: tests/compile_pip_requirements
+ platform: ubuntu2004
+ shell_commands:
+ # Make a change to the locked requirements and then assert that //:requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:requirements.update"
+ - "git diff --exit-code"
+ # Make a change to the locked requirements and then assert that //:os_specific_requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock_linux.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:os_specific_requirements.update"
+ - "git diff --exit-code"
+ integration_test_compile_pip_requirements_debian:
+ <<: *reusable_build_test_all
+ name: compile_pip_requirements integration tests on Debian
+ working_directory: tests/compile_pip_requirements
+ platform: debian11
+ shell_commands:
+ # Make a change to the locked requirements and then assert that //:requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:requirements.update"
+ - "git diff --exit-code"
+ # Make a change to the locked requirements and then assert that //:os_specific_requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock_linux.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:os_specific_requirements.update"
+ - "git diff --exit-code"
+ integration_test_compile_pip_requirements_macos:
+ <<: *reusable_build_test_all
+ name: compile_pip_requirements integration tests on macOS
+ working_directory: tests/compile_pip_requirements
+ platform: macos
+ shell_commands:
+ # Make a change to the locked requirements and then assert that //:requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:requirements.update"
+ - "git diff --exit-code"
+ # Make a change to the locked requirements and then assert that //:os_specific_requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock_darwin.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:os_specific_requirements.update"
+ - "git diff --exit-code"
+ integration_test_compile_pip_requirements_windows:
+ <<: *reusable_build_test_all
+ name: compile_pip_requirements integration tests on Windows
+ working_directory: tests/compile_pip_requirements
+ platform: windows
+ shell_commands:
+ # Make a change to the locked requirements and then assert that //:requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:requirements.update"
+ - "git diff --exit-code"
+ # Make a change to the locked requirements and then assert that //:os_specific_requirements.update does the
+ # right thing.
+ - "echo '' > requirements_lock_windows.txt"
+ - "! git diff --exit-code"
+ - "bazel run //:os_specific_requirements.update"
+ - "git diff --exit-code"
+
+ integration_test_pip_repository_entry_points_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: pip_repository_entry_points integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: tests/pip_repository_entry_points
+ platform: ubuntu2004
+ integration_test_pip_repository_entry_points_ubuntu:
+ <<: *reusable_build_test_all
+ name: pip_repository_entry_points integration tests on Ubuntu
+ working_directory: tests/pip_repository_entry_points
+ platform: ubuntu2004
+ integration_test_pip_repository_entry_points_debian:
+ <<: *reusable_build_test_all
+ name: pip_repository_entry_points integration tests on Debian
+ working_directory: tests/pip_repository_entry_points
+ platform: debian11
+ integration_test_pip_repository_entry_points_macos:
+ <<: *reusable_build_test_all
+ name: pip_repository_entry_points integration tests on macOS
+ working_directory: tests/pip_repository_entry_points
+ platform: macos
+ integration_test_pip_repository_entry_points_windows:
+ <<: *reusable_build_test_all
+ name: pip_repository_entry_points integration tests on Windows
+ working_directory: tests/pip_repository_entry_points
+ platform: windows
+
+ integration_test_ignore_root_user_error_ubuntu_min:
+ <<: *minimum_supported_version
+ <<: *reusable_build_test_all
+ name: ignore_root_user_error integration tests on Ubuntu using minimum supported Bazel version
+ working_directory: tests/ignore_root_user_error
+ platform: ubuntu2004
+ integration_test_ignore_root_user_error_ubuntu:
+ <<: *reusable_build_test_all
+ name: ignore_root_user_error integration tests on Ubuntu
+ working_directory: tests/ignore_root_user_error
+ platform: ubuntu2004
+ integration_test_ignore_root_user_error_debian:
+ <<: *reusable_build_test_all
+ name: ignore_root_user_error integration tests on Debian
+ working_directory: tests/ignore_root_user_error
+ platform: debian11
+ integration_test_ignore_root_user_error_macos:
+ <<: *reusable_build_test_all
+ name: ignore_root_user_error integration tests on macOS
+ working_directory: tests/ignore_root_user_error
+ platform: macos
+ integration_test_ignore_root_user_error_windows:
+ <<: *reusable_build_test_all
+ name: ignore_root_user_error integration tests on Windows
+ working_directory: tests/ignore_root_user_error
+ platform: windows
+
+ integration_compile_pip_requirements_test_from_external_repo_ubuntu_min:
+ <<: *minimum_supported_version
+ name: compile_pip_requirements test from external repo on Ubuntu using minimum supported Bazel version
+ working_directory: tests/compile_pip_requirements_test_from_external_workspace
+ platform: ubuntu2004
+ shell_commands:
+ # Assert that @external_repository//:requirements_test does the right thing.
+ - "bazel test @external_repository//..."
+ integration_compile_pip_requirements_test_from_external_repo_ubuntu:
+ name: compile_pip_requirements test from external repo on Ubuntu
+ working_directory: tests/compile_pip_requirements_test_from_external_workspace
+ platform: ubuntu2004
+ shell_commands:
+ # Assert that @external_repository//:requirements_test does the right thing.
+ - "bazel test @external_repository//..."
+ integration_compile_pip_requirements_test_from_external_repo_debian:
+ name: compile_pip_requirements test from external repo on Debian
+ working_directory: tests/compile_pip_requirements_test_from_external_workspace
+ platform: debian11
+ shell_commands:
+ # Assert that @external_repository//:requirements_test does the right thing.
+ - "bazel test @external_repository//..."
+ integration_compile_pip_requirements_test_from_external_repo_macos:
+ name: compile_pip_requirements test from external repo on macOS
+ working_directory: tests/compile_pip_requirements_test_from_external_workspace
+ platform: macos
+ shell_commands:
+ # Assert that @external_repository//:requirements_test does the right thing.
+ - "bazel test @external_repository//..."
+ integration_compile_pip_requirements_test_from_external_repo_windows:
+ name: compile_pip_requirements test from external repo on Windows
+ working_directory: tests/compile_pip_requirements_test_from_external_workspace
+ platform: windows
+ shell_commands:
+ # Assert that @external_repository//:requirements_test does the right thing.
+ - "bazel test @external_repository//..."
+
diff --git a/.bazelignore b/.bazelignore
new file mode 100644
index 0000000..135f709
--- /dev/null
+++ b/.bazelignore
@@ -0,0 +1,11 @@
+# Normally these are ignored, but if you're using a custom
+# build of Bazel with a custom --output_user_root value, Bazel
+# tries to follow the symlinks of the other builds and finds
+# the WORKSPACE, BUILD, etc files and tries to build them.
+bazel-rules_python
+bazel-bin
+bazel-out
+bazel-testlogs
+examples/bzlmod/bazel-bzlmod
+examples/bzlmod_build_file_generation/bazel-bzlmod_build_file_generation
+examples/py_proto_library/bazel-py_proto_library
diff --git a/.bazelrc b/.bazelrc
new file mode 100644
index 0000000..87fa6d5
--- /dev/null
+++ b/.bazelrc
@@ -0,0 +1,21 @@
+# For bazel-in-bazel testing
+# Trick bazel into treating BUILD files under examples/* as being regular files
+# This lets us glob() up all the files inside the examples to make them inputs to tests
+# (Note, we cannot use `common --deleted_packages` because the bazel version command doesn't support it)
+# To update these lines, run tools/bazel_integration_test/update_deleted_packages.sh
+build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_point,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_install,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
+query --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_point,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/whl_mods,examples/bzlmod_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/multi_python_versions/libs/my_lib,examples/multi_python_versions/requirements,examples/multi_python_versions/tests,examples/pip_install,examples/pip_parse,examples/pip_parse_vendored,examples/pip_repository_annotations,examples/py_proto_library,tests/compile_pip_requirements,tests/compile_pip_requirements_test_from_external_workspace,tests/ignore_root_user_error,tests/pip_repository_entry_points
+
+test --test_output=errors
+
+# Do NOT implicitly create empty __init__.py files in the runfiles tree.
+# By default, these are created in every directory containing Python source code
+# or shared libraries, and every parent directory of those directories,
+# excluding the repo root directory. With this flag set, we are responsible for
+# creating (possibly empty) __init__.py files and adding them to the srcs of
+# Python targets as required.
+build --incompatible_default_to_explicit_init_py
+
+# Windows makes use of runfiles for some rules
+build --enable_runfiles
+startup --windows_enable_symlinks
diff --git a/.bazelversion b/.bazelversion
new file mode 100644
index 0000000..09b254e
--- /dev/null
+++ b/.bazelversion
@@ -0,0 +1 @@
+6.0.0
diff --git a/.bcr/config.yml b/.bcr/config.yml
new file mode 100644
index 0000000..7bdd70f
--- /dev/null
+++ b/.bcr/config.yml
@@ -0,0 +1,18 @@
+# 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.
+
+fixedReleaser:
+ login: f0rmiga
+ email: thulio@aspect.dev
+moduleRoots: [".", "gazelle"]
diff --git a/.bcr/gazelle/metadata.template.json b/.bcr/gazelle/metadata.template.json
new file mode 100644
index 0000000..9cd4291
--- /dev/null
+++ b/.bcr/gazelle/metadata.template.json
@@ -0,0 +1,20 @@
+{
+ "homepage": "https://github.com/bazelbuild/rules_python",
+ "maintainers": [
+ {
+ "name": "Richard Levasseur",
+ "email": "rlevasseur@google.com",
+ "github": "rickeylev"
+ },
+ {
+ "name": "Thulio Ferraz Assis",
+ "email": "thulio@aspect.dev",
+ "github": "f0rmiga"
+ }
+ ],
+ "repository": [
+ "github:bazelbuild/rules_python"
+ ],
+ "versions": [],
+ "yanked_versions": {}
+}
diff --git a/.bcr/gazelle/presubmit.yml b/.bcr/gazelle/presubmit.yml
new file mode 100644
index 0000000..037055d
--- /dev/null
+++ b/.bcr/gazelle/presubmit.yml
@@ -0,0 +1,27 @@
+# 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.
+
+bcr_test_module:
+ module_path: "../examples/bzlmod_build_file_generation"
+ matrix:
+ platform: ["debian11", "macos", "ubuntu2004", "windows"]
+ tasks:
+ run_tests:
+ name: "Run test module"
+ platform: ${{ platform }}
+ build_targets:
+ - "//..."
+ - ":modules_map"
+ test_targets:
+ - "//..."
diff --git a/.bcr/gazelle/source.template.json b/.bcr/gazelle/source.template.json
new file mode 100644
index 0000000..cf06458
--- /dev/null
+++ b/.bcr/gazelle/source.template.json
@@ -0,0 +1,5 @@
+{
+ "integrity": "",
+ "strip_prefix": "{REPO}-{VERSION}/gazelle",
+ "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/rules_python-{TAG}.tar.gz"
+}
diff --git a/.bcr/metadata.template.json b/.bcr/metadata.template.json
new file mode 100644
index 0000000..7b16b53
--- /dev/null
+++ b/.bcr/metadata.template.json
@@ -0,0 +1,20 @@
+{
+ "homepage": "https://github.com/bazelbuild/rules_python",
+ "maintainers": [
+ {
+ "name": "Richard Levasseur",
+ "email": "rlevasseur@google.com",
+ "github": "rickeylev"
+ },
+ {
+ "name": "Thulio Ferraz Assis",
+ "email": "thulio@aspect.dev",
+ "github": "f0rmiga"
+ }
+ ],
+ "repository": [
+ "github:bazelbuild/rules_python"
+ ],
+ "versions": [],
+ "yanked_versions": {}
+}
diff --git a/.bcr/presubmit.yml b/.bcr/presubmit.yml
new file mode 100644
index 0000000..252df6b
--- /dev/null
+++ b/.bcr/presubmit.yml
@@ -0,0 +1,24 @@
+# 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.
+
+bcr_test_module:
+ module_path: "examples/bzlmod"
+ matrix:
+ platform: ["debian11", "macos", "ubuntu2004", "windows"]
+ tasks:
+ run_tests:
+ name: "Run test module"
+ platform: ${{ platform }}
+ test_targets:
+ - "//..."
diff --git a/.bcr/source.template.json b/.bcr/source.template.json
new file mode 100644
index 0000000..c23b765
--- /dev/null
+++ b/.bcr/source.template.json
@@ -0,0 +1,5 @@
+{
+ "integrity": "",
+ "strip_prefix": "{REPO}-{VERSION}",
+ "url": "https://github.com/{OWNER}/{REPO}/releases/download/{TAG}/rules_python-{TAG}.tar.gz"
+}
diff --git a/.ci/rules_python.json b/.ci/rules_python.json
new file mode 100644
index 0000000..f59edf3
--- /dev/null
+++ b/.ci/rules_python.json
@@ -0,0 +1,9 @@
+[
+ {
+ "variation": "",
+ "configurations": [
+ {"node": "linux-x86_64"},
+ {"node": "darwin-x86_64"}
+ ]
+ }
+]
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
new file mode 100644
index 0000000..cc87547
--- /dev/null
+++ b/.git-blame-ignore-revs
@@ -0,0 +1,2 @@
+aedcef6a8f039a06c87ea33cdb722779e3170ea3
+b381dcb8880358a6aa63b44560f43377f8bffaf3
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..64d09ff
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+docs/*.md linguist-generated=true
+tools/publish/*.txt linguist-generated=true
diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS
new file mode 100644
index 0000000..3449bcf
--- /dev/null
+++ b/.github/CODEOWNERS
@@ -0,0 +1,15 @@
+# NB: Last matching rule takes precedence in CODEOWNERS.
+
+* @rickeylev
+
+# Directory containing the Gazelle extension and Go code.
+/gazelle/ @f0rmiga
+/examples/build_file_generation/ @f0rmiga
+
+# Toolchains
+/python/repositories.bzl @f0rmiga
+/python/private/toolchains_repo.bzl @f0rmiga
+/python/tests/toolchains/ @f0rmiga
+
+# pip_parse related code
+/python/pip_install/ @hrfuller
diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..a8920bc
--- /dev/null
+++ b/.github/CODE_OF_CONDUCT.md
@@ -0,0 +1,76 @@
+# Contributor Covenant Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, sex characteristics, gender identity and expression,
+level of experience, education, socio-economic status, nationality, personal
+appearance, race, religion, or sexual identity and orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+ advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+ address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at bazel-discuss@googlegroups.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
+
+[homepage]: https://www.contributor-covenant.org
+
+For answers to common questions about this code of conduct, see
+https://www.contributor-covenant.org/faq
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..697dfd5
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,76 @@
+---
+name: "\U0001F41EBug report"
+about: Report a bug in rules_python
+---
+<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
+
+Oh hi there! 😄
+
+To expedite issue processing please search open and closed issues before submitting a new one.
+Existing issues often contain information about workarounds, resolution, or progress updates.
+
+🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
+
+
+# 🐞 bug report
+
+### Affected Rule
+
+<!-- Can you pin-point one or more rules as the source of the bug? -->
+<!-- ✍️edit: --> The issue is caused by the rule:
+
+
+### Is this a regression?
+
+<!-- Did this behavior use to work in the previous version? -->
+<!-- ✍️--> Yes, the previous version in which this bug was not present was: ....
+
+
+### Description
+
+<!-- ✍️--> A clear and concise description of the problem...
+
+
+## 🔬 Minimal Reproduction
+
+<!--
+Please create and share minimal reproduction of the issue. For the purpose you can create a GitHub repository and share a link. Make sure you don't upload any confidential files.
+-->
+
+## 🔥 Exception or Error
+
+<pre><code>
+<!-- If the issue is accompanied by an exception or an error, please share it below: -->
+<!-- ✍️-->
+
+</code></pre>
+
+
+## 🌍 Your Environment
+
+**Operating System:**
+
+<pre>
+ <code>
+
+ </code>
+</pre>
+
+**Output of `bazel version`:**
+
+<pre>
+ <code>
+
+ </code>
+</pre>
+
+**Rules_python version:**
+
+<pre>
+ <code>
+
+ </code>
+</pre>
+
+**Anything else relevant?**
+
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..3612d72
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,45 @@
+---
+name: "\U0001F680Feature request"
+about: Suggest a feature for rules_python
+
+---
+<!--🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅
+
+Oh hi there! 😄
+
+To expedite issue processing please search open and closed issues before submitting a new one.
+Existing issues often contain information about workarounds, resolution, or progress updates.
+
+🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅🔅-->
+
+
+# 🚀 feature request
+
+### Relevant Rules
+
+<!-- Tell us if you want to add a feature to an existing rule or add a new rule -->
+
+<!--
+
+If you're looking for a new rule first make sure you check awesome-bazel for an
+curated list of existing bazel rules https://github.com/jin/awesome-bazel
+
+If you don't find what you're looking for there, before opening a feature request make sure
+this repository is the right place for that.
+
+-->
+
+### Description
+
+<!-- ✍️--> A clear and concise description of the problem or missing capability...
+
+
+### Describe the solution you'd like
+
+<!-- ✍️--> If you have a solution in mind, please describe it.
+
+
+### Describe alternatives you've considered
+
+<!-- ✍️--> Have you considered any alternative solutions or workarounds?
+
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
new file mode 100644
index 0000000..0d305b8
--- /dev/null
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -0,0 +1,11 @@
+PR Instructions/requirements
+* Title uses `type: description` format. See CONTRIBUTING.md for types.
+* Common types are: build, docs, feat, fix, refactor, revert, test
+* Breaking changes include "!" after the type and a "BREAKING CHANGES:"
+ section at the bottom.
+* Body text describes:
+ * Why this change is being made, briefly.
+ * Before and after behavior, as applicable
+ * References issue number, as applicable
+* Update docs and tests, as applicable
+* Delete these instructions prior to sending the PR
diff --git a/.github/workflows/create_archive_and_notes.sh b/.github/workflows/create_archive_and_notes.sh
new file mode 100755
index 0000000..f7a291a
--- /dev/null
+++ b/.github/workflows/create_archive_and_notes.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+# 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.
+
+set -o errexit -o nounset -o pipefail
+
+# Set by GH actions, see
+# https://docs.github.com/en/actions/learn-github-actions/environment-variables#default-environment-variables
+TAG=${GITHUB_REF_NAME}
+# A prefix is added to better match the GitHub generated archives.
+PREFIX="rules_python-${TAG}"
+ARCHIVE="rules_python-$TAG.tar.gz"
+git archive --format=tar --prefix=${PREFIX}/ ${TAG} | gzip > $ARCHIVE
+SHA=$(shasum -a 256 $ARCHIVE | awk '{print $1}')
+
+cat > release_notes.txt << EOF
+## Using Bzlmod with Bazel 6
+
+**NOTE: bzlmod support is still beta. APIs subject to change.**
+
+Add to your \`MODULE.bazel\` file:
+
+\`\`\`starlark
+bazel_dep(name = "rules_python", version = "${TAG}")
+
+pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
+
+pip.parse(
+ name = "pip",
+ requirements_lock = "//:requirements_lock.txt",
+)
+
+use_repo(pip, "pip")
+\`\`\`
+
+## Using WORKSPACE
+
+Paste this snippet into your \`WORKSPACE\` file:
+
+\`\`\`starlark
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+ name = "rules_python",
+ sha256 = "${SHA}",
+ strip_prefix = "${PREFIX}",
+ url = "https://github.com/bazelbuild/rules_python/releases/download/${TAG}/rules_python-${TAG}.tar.gz",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories")
+
+py_repositories()
+\`\`\`
+
+### Gazelle plugin
+
+Paste this snippet into your \`WORKSPACE\` file:
+
+\`\`\`starlark
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+http_archive(
+ name = "rules_python_gazelle_plugin",
+ sha256 = "${SHA}",
+ strip_prefix = "${PREFIX}/gazelle",
+ url = "https://github.com/bazelbuild/rules_python/releases/download/${TAG}/rules_python-${TAG}.tar.gz",
+)
+
+# To compile the rules_python gazelle extension from source,
+# we must fetch some third-party go dependencies that it uses.
+
+load("@rules_python_gazelle_plugin//:deps.bzl", _py_gazelle_deps = "gazelle_deps")
+
+_py_gazelle_deps()
+\`\`\`
+EOF
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..eb23bc8
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,46 @@
+# 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.
+
+# Cut a release whenever a new tag is pushed to the repo.
+name: Release
+
+on:
+ push:
+ tags:
+ - "*.*.*"
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v2
+ - name: Create release archive and notes
+ run: .github/workflows/create_archive_and_notes.sh
+ - name: Publish wheel dist
+ env:
+ # This special value tells pypi that the user identity is supplied within the token
+ TWINE_USERNAME: __token__
+ # Note, the PYPI_API_TOKEN is for the rules-python pypi user, added by @rickylev on
+ # https://github.com/bazelbuild/rules_python/settings/secrets/actions
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
+ run: bazel run --stamp --embed_label=${{ github.ref_name }} //python/runfiles:wheel.publish
+ - name: Release
+ uses: softprops/action-gh-release@v1
+ with:
+ # Use GH feature to populate the changelog automatically
+ generate_release_notes: true
+ body_path: release_notes.txt
+ fail_on_unmatched_files: true
+ files: rules_python-*.tar.gz
diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml
new file mode 100644
index 0000000..8d388e2
--- /dev/null
+++ b/.github/workflows/stale.yml
@@ -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.
+
+# See https://github.com/marketplace/actions/close-stale-issues
+
+name: Mark stale issues and pull requests
+
+on:
+ schedule:
+ # run at 22:45 UTC daily
+ - cron: "45 22 * * *"
+
+jobs:
+ stale:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/stale@v3
+ with:
+ repo-token: ${{ secrets.GITHUB_TOKEN }}
+
+ # NB: We start with very long duration while trimming existing issues,
+ # with the hope to reduce when/if we get better at keeping up with user support.
+
+ # The number of days old an issue can be before marking it stale.
+ days-before-stale: 180
+ # Number of days of inactivity before a stale issue is closed
+ days-before-close: 30
+
+ # If an issue/PR is assigned, trust the assignee to stay involved
+ # Can revisit if these get stale
+ exempt-all-assignees: true
+ # Issues with these labels will never be considered stale
+ exempt-issue-labels: "need: discussion,cleanup"
+
+ # Label to use when marking an issue as stale
+ stale-issue-label: 'Can Close?'
+ stale-pr-label: 'Can Close?'
+
+ stale-issue-message: >
+ This issue has been automatically marked as stale because it has not had
+ any activity for 180 days.
+ It will be closed if no further activity occurs in 30 days.
+
+ Collaborators can add an assignee to keep this open indefinitely.
+ Thanks for your contributions to rules_python!
+
+ stale-pr-message: >
+ This Pull Request has been automatically marked as stale because it has not had
+ any activity for 180 days.
+ It will be closed if no further activity occurs in 30 days.
+
+ Collaborators can add an assignee to keep this open indefinitely.
+ Thanks for your contributions to rules_python!
+
+ close-issue-message: >
+ This issue was automatically closed because it went 30 days without a reply
+ since it was labeled "Can Close?"
+
+ close-pr-message: >
+ This PR was automatically closed because it went 30 days without a reply
+ since it was labeled "Can Close?"
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..bf901e2
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,47 @@
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# Emacs garbage
+*~
+
+# Bazel directories
+/bazel-*
+/bazel-bin
+/bazel-genfiles
+/bazel-out
+/bazel-testlogs
+user.bazelrc
+
+# vim swap files
+*.swp
+*.swo
+
+# Python cache
+**/__pycache__/
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
new file mode 100644
index 0000000..e501053
--- /dev/null
+++ b/.pre-commit-config.yaml
@@ -0,0 +1,47 @@
+# 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.
+
+# See CONTRIBUTING.md for instructions.
+# See https://pre-commit.com for more information
+# See https://pre-commit.com/hooks.html for more hooks
+repos:
+ - repo: https://github.com/keith/pre-commit-buildifier
+ rev: 6.1.0
+ hooks:
+ - id: buildifier
+ args: &args
+ # Keep this argument in sync with .bazelci/presubmit.yaml
+ - --warnings=all
+ - id: buildifier-lint
+ args: *args
+ - repo: https://github.com/pycqa/isort
+ rev: 5.12.0
+ hooks:
+ - id: isort
+ name: isort (python)
+ args:
+ - --profile
+ - black
+ - repo: https://github.com/psf/black
+ rev: 23.1.0
+ hooks:
+ - id: black
+ - repo: local
+ hooks:
+ - id: update-deleted-packages
+ name: Update deleted packages
+ language: script
+ entry: ./tools/bazel_integration_test/update_deleted_packages.sh
+ files: ^((examples|tests)/*/(MODULE.bazel|WORKSPACE|WORKSPACE.bzlmod|BUILD.bazel)|.bazelrc|tools/bazel_integration_test/update_deleted_packages.sh)$
+ pass_filenames: false
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..8f95963
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,9 @@
+# This the official list of Bazel authors for copyright purposes.
+# This file is distinct from the CONTRIBUTORS files.
+# See the latter for an explanation.
+
+# Names should be added to this file as:
+# Name or Organization <email address>
+# The email address is not required for organizations.
+
+Google Inc.
diff --git a/BUILD.bazel b/BUILD.bazel
new file mode 100644
index 0000000..35a3df8
--- /dev/null
+++ b/BUILD.bazel
@@ -0,0 +1,87 @@
+# Copyright 2017 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(":version.bzl", "BAZEL_VERSION")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+exports_files([
+ "LICENSE",
+ "version.bzl",
+])
+
+filegroup(
+ name = "distribution",
+ srcs = [
+ "BUILD.bazel",
+ "MODULE.bazel",
+ "WORKSPACE",
+ "internal_deps.bzl",
+ "internal_setup.bzl",
+ "version.bzl",
+ "//python:distribution",
+ "//python/pip_install:distribution",
+ "//tools:distribution",
+ "@rules_python_gazelle_plugin//:distribution",
+ ],
+ visibility = [
+ "//examples:__pkg__",
+ "//python/tests/toolchains:__pkg__",
+ "//tests:__pkg__",
+ ],
+)
+
+# Reexport of all bzl files used to allow downstream rules to generate docs
+# without shipping with a dependency on Skylib
+filegroup(
+ name = "bzl",
+ srcs = [
+ "//python/pip_install:bzl",
+ "//python:bzl",
+ # Requires Bazel 0.29 onward for public visibility of these .bzl files.
+ "@bazel_tools//tools/python:python_version.bzl",
+ "@bazel_tools//tools/python:srcs_version.bzl",
+ "@bazel_tools//tools/python:toolchain.bzl",
+ "@bazel_tools//tools/python:utils.bzl",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+genrule(
+ name = "assert_bazelversion",
+ srcs = [".bazelversion"],
+ outs = ["assert_bazelversion_test.sh"],
+ cmd = """\
+set -o errexit -o nounset -o pipefail
+current=$$(cat "$(execpath .bazelversion)")
+cat > "$@" <<EOF
+#!/usr/bin/env bash
+set -o errexit -o nounset -o pipefail
+if [[ \"$${{current}}\" != \"{expected}\" ]]; then
+ >&2 echo "ERROR: current bazel version '$${{current}}' is not the expected '{expected}'"
+ exit 1
+fi
+EOF
+""".format(
+ expected = BAZEL_VERSION,
+ ),
+ executable = True,
+)
+
+sh_test(
+ name = "assert_bazelversion_test",
+ srcs = [":assert_bazelversion_test.sh"],
+)
diff --git a/BZLMOD_SUPPORT.md b/BZLMOD_SUPPORT.md
new file mode 100644
index 0000000..d3d0607
--- /dev/null
+++ b/BZLMOD_SUPPORT.md
@@ -0,0 +1,61 @@
+# Bzlmod support
+
+## `rules_python` `bzlmod` support
+
+- Status: Beta
+- Full Feature Parity: No
+
+Some features are missing or broken, and the public APIs are not yet stable.
+
+## Configuration
+
+The releases page will give you the latest version number, and a basic example. The release page is located [here](/bazelbuild/rules_python/releases).
+
+## What is bzlmod?
+
+> Bazel supports external dependencies, source files (both text and binary) used in your build that are not from your workspace. For example, they could be a ruleset hosted in a GitHub repo, a Maven artifact, or a directory on your local machine outside your current workspace.
+>
+> As of Bazel 6.0, there are two ways to manage external dependencies with Bazel: the traditional, repository-focused WORKSPACE system, and the newer module-focused MODULE.bazel system (codenamed Bzlmod, and enabled with the flag `--enable_bzlmod`). The two systems can be used together, but Bzlmod is replacing the WORKSPACE system in future Bazel releases.
+> -- <cite>https://bazel.build/external/overview</cite>
+
+## Examples
+
+We have two examples that demonstrate how to configure `bzlmod`.
+
+The first example is in [examples/bzlmod](examples/bzlmod), and it demonstrates basic bzlmod configuration.
+A user does not use `local_path_override` stanza and would define the version in the `bazel_dep` line.
+
+A second example, in [examples/bzlmod_build_file_generation](examples/bzlmod_build_file_generation) demonstrates the use of `bzlmod` to configure `gazelle` support for `rules_python`.
+
+## Feature parity
+
+This rule set does not have full feature partity with the older `WORKSPACE` type configuration:
+
+1. Gazelle does not support finding deps in sub-modules. For instance we can have a dep like ` "@our_other_module//other_module/pkg:lib",` in a `py_test` definition.
+2. We have some features that are still not fully flushed out, and the user interface may change.
+
+Check ["issues"](/bazelbuild/rules_python/issues) for an up to date list.
+
+## Differences in behavior from WORKSPACE
+
+### Default toolchain is not the local system Python
+
+Under bzlmod, the default toolchain is no longer based on the locally installed
+system Python. Instead, a recent Python version using the pre-built,
+standalone runtimes are used.
+
+If you need the local system Python to be your toolchain, then it's suggested
+that you setup and configure your own toolchain and register it. Note that using
+the local system's Python is not advised because will vary between users and
+platforms.
+
+If you want to use the same toolchain as what WORKSPACE used, then manually
+register the builtin Bazel Python toolchain by doing
+`register_toolchains("@bazel_tools//tools/python:autodetecting_toolchain")`.
+**IMPORTANT: this should only be done in a root module, and may intefere with
+the toolchains rules_python registers**.
+
+NOTE: Regardless of your toolchain, due to
+[#691](https://github.com/bazelbuild/rules_python/issues/691), `rules_python`
+still relies on a local Python being available to bootstrap the program before
+handing over execution to the toolchain Python.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..54ecfb0
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,205 @@
+# How to contribute
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Getting started
+
+Before we can work on the code, we need to get a copy of it and setup some
+local environment and tools.
+
+First, fork the code to your user and clone your fork. This gives you a private
+playground where you can do any edits you'd like. For this guide, we'll use
+the [GitHub `gh` tool](https://github.com/cli/cli)
+([Linux install](https://github.com/cli/cli/blob/trunk/docs/install_linux.md)).
+(More advanced users may prefer the GitHub UI and raw `git` commands).
+
+```shell
+gh repo fork bazelbuild/rules_python --clone --remote
+```
+
+Next, make sure you have a new enough version of Python installed that supports the
+various code formatters and other devtools. For a quick start,
+[install pyenv](https://github.com/pyenv/pyenv-installer) and
+at least Python 3.9.15:
+
+```shell
+curl https://pyenv.run | bash
+pyenv install 3.9.15
+pyenv shell 3.9.15
+```
+
+## Development workflow
+
+It's suggested that you create what is called a "feature/topic branch" in your
+fork when you begin working on code you want to eventually send or code review.
+
+```
+git checkout main # Start our branch from the latest code
+git checkout -b my-feature # Create and switch to our feature branch
+git push origin my-feature # Cause the branch to be created in your fork.
+```
+
+From here, you then edit code and commit to your local branch. If you want to
+save your work to github, you use `git push` to do so:
+
+```
+git push origin my-feature
+```
+
+Once the code is in your github repo, you can then turn it into a Pull Request
+to the actual rules_python project and begin the code review process.
+
+
+## Running tests
+
+Running tests is particularly easy thanks to Bazel, simply run:
+
+```
+bazel test //...
+```
+
+And it will run all the tests it can find. The first time you do this, it will
+probably take long time because various dependencies will need to be downloaded
+and setup. Subsequent runs will be faster, but there are many tests, and some of
+them are slow. If you're working on a particular area of code, you can run just
+the tests in those directories instead, which can speed up your edit-run cycle.
+
+Note that there are tests to verify generated documentation is correct -- if
+you're modifying the signature of a public function, these tests will likely
+fail and you'll need to [regenerate the api docs](#documentation).
+
+## Formatting
+
+Starlark files should be formatted by
+[buildifier](https://github.com/bazelbuild/buildtools/blob/master/buildifier/README.md).
+Otherwise the Buildkite CI will fail with formatting/linting violations.
+We suggest using a pre-commit hook to automate this.
+First [install pre-commit](https://pre-commit.com/#installation),
+then run
+
+```shell
+pre-commit install
+```
+
+### Running buildifer manually
+
+You can also run buildifier manually. To do this,
+[install buildifier](https://github.com/bazelbuild/buildtools/blob/master/buildifier/README.md),
+and run the following command:
+
+```shell
+$ buildifier --lint=fix --warnings=native-py -warnings=all WORKSPACE
+```
+
+Replace the argument "WORKSPACE" with the file that you are linting.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution,
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code reviews
+
+All submissions, including submissions by project members, require review. We
+use GitHub pull requests for this purpose. Consult [GitHub Help] for more
+information on using pull requests.
+
+[GitHub Help]: https://help.github.com/articles/about-pull-requests/
+
+### Commit messages
+
+Commit messages (upon merging) and PR messages should follow the [Conventional
+Commits](https://www.conventionalcommits.org/) style:
+
+```
+type(scope)!: <summary>
+
+<body>
+
+BREAKING CHANGE: <summary>
+```
+
+Where `(scope)` is optional, and `!` is only required if there is a breaking change.
+If a breaking change is introduced, then `BREAKING CHANGE:` is required.
+
+Common `type`s:
+
+* `build:` means it affects the building or development workflow.
+* `docs:` means only documentation is being added, updated, or fixed.
+* `feat:` means a user-visible feature is being added.
+* `fix:` means a user-visible behavior is being fixed.
+* `refactor:` means some sort of code cleanup that doesn't change user-visible behavior.
+* `revert:` means a prior change is being reverted in some way.
+* `test:` means only tests are being added.
+
+For the full details of types, see
+[Conventional Commits](https://www.conventionalcommits.org/).
+
+## Generated files
+
+Some checked-in files are generated and need to be updated when a new PR is
+merged.
+
+### Documentation
+
+To regenerate the content under the `docs/` directory, run this command:
+
+```shell
+bazel run //docs:update
+```
+
+This needs to be done whenever the docstrings in the corresponding .bzl files
+are changed; a test failure will remind you to run this command when needed.
+
+## Core rules
+
+The bulk of this repo is owned and maintained by the Bazel Python community.
+However, since the core Python rules (`py_binary` and friends) are still
+bundled with Bazel itself, the Bazel team retains ownership of their stubs in
+this repository. This will be the case at least until the Python rules are
+fully migrated to Starlark code.
+
+Practically, this means that a Bazel team member should approve any PR
+concerning the core Python logic. This includes everything under the `python/`
+directory except for `pip.bzl` and `requirements.txt`.
+
+Issues should be triaged as follows:
+
+- Anything concerning the way Bazel implements the core Python rules should be
+ filed under [bazelbuild/bazel](https://github.com/bazelbuild/bazel), using
+ the label `team-Rules-python`.
+
+- If the issue specifically concerns the rules_python stubs, it should be filed
+ here in this repository and use the label `core-rules`.
+
+- Anything else, such as feature requests not related to existing core rules
+ functionality, should also be filed in this repository but without the
+ `core-rules` label.
+
+## FAQ
+
+### Installation errors when during `git commit`
+
+If you did `pre-commit install`, various tools are run when you do `git commit`.
+This might show as an error such as:
+
+```
+[INFO] Installing environment for https://github.com/psf/black.
+[INFO] Once installed this environment will be reused.
+[INFO] This may take a few minutes...
+An unexpected error has occurred: CalledProcessError: command: ...
+```
+
+To fix, you'll need to figure out what command is failing and why. Because these
+are tools that run locally, its likely you'll need to fix something with your
+environment or the installation of the tools. For Python tools (e.g. black or
+isort), you can try using a different Python version in your shell by using
+tools such as [pyenv](https://github.com/pyenv/pyenv).
diff --git a/CONTRIBUTORS b/CONTRIBUTORS
new file mode 100644
index 0000000..346a333
--- /dev/null
+++ b/CONTRIBUTORS
@@ -0,0 +1,12 @@
+# People who have agreed to one of the CLAs and can contribute patches.
+# The AUTHORS file lists the copyright holders; this file
+# lists people. For example, Google employees are listed here
+# but not in AUTHORS, because Google holds the copyright.
+#
+# https://developers.google.com/open-source/cla/individual
+# https://developers.google.com/open-source/cla/corporate
+#
+# Names should be added to this file as:
+# Name <email address>
+
+Matthew Moore <mattmoor@google.com>
diff --git a/DEVELOPING.md b/DEVELOPING.md
new file mode 100644
index 0000000..2972d96
--- /dev/null
+++ b/DEVELOPING.md
@@ -0,0 +1,39 @@
+# For Developers
+
+## Releasing
+
+Start from a clean checkout at `main`.
+
+Before running through the release it's good to run the build and the tests locally, and make sure CI is passing. You can
+also test-drive the commit in an existing Bazel workspace to sanity check functionality.
+
+#### Steps
+1. [Determine the next semantic version number](#determining-semantic-version)
+1. Create a tag and push, e.g. `git tag 0.5.0 upstream/main && git push upstream --tags`
+ NOTE: Pushing the tag will trigger release automation.
+1. Watch the release automation run on https://github.com/bazelbuild/rules_python/actions
+1. Add missing information to the release notes. The automatic release note
+ generation only includes commits associated with issues.
+
+#### Determining Semantic Version
+
+**rules_python** is currently using [Zero-based versioning](https://0ver.org/) and thus backwards-incompatible API
+changes still come under the minor-version digit. So releases with API changes and new features bump the minor, and
+those with only bug fixes and other minor changes bump the patch digit.
+
+To find if there were any features added or incompatible changes made, review
+the commit history. This can be done using github by going to the url:
+`https://github.com/bazelbuild/rules_python/compare/<VERSION>...main`.
+
+#### After release creation in Github
+
+1. Ping @philwo to get the new release added to mirror.bazel.build. See [this comment on issue #400](https://github.com/bazelbuild/rules_python/issues/400#issuecomment-779159530) for more context.
+1. Announce the release in the #python channel in the Bazel slack (bazelbuild.slack.com).
+
+## Secrets
+
+### PyPI user rules-python
+
+Part of the release process uploads packages to PyPI as the user `rules-python`.
+This account is managed by Google; contact rules-python-pyi@google.com if
+something needs to be done with the PyPI account.
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ 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.
diff --git a/METADATA b/METADATA
new file mode 100644
index 0000000..0b420e0
--- /dev/null
+++ b/METADATA
@@ -0,0 +1,15 @@
+name: "bazelbuild-rules_python"
+description:
+ "A repository of Starlark implementation of Python rules in Bazel"
+
+third_party {
+ url {
+ type: GIT
+ value: "https://github.com/bazelbuild/rules_python"
+ }
+ version: "4082693e23ec9615f3e9b2ed9fae542e2b3bed12"
+ last_upgrade_date { year: 2023 month: 7 day: 24 }
+ license_type: NOTICE
+}
+
+
diff --git a/MODULE.bazel b/MODULE.bazel
new file mode 100644
index 0000000..b7a0411
--- /dev/null
+++ b/MODULE.bazel
@@ -0,0 +1,49 @@
+module(
+ name = "rules_python",
+ version = "0.0.0",
+ compatibility_level = 1,
+)
+
+bazel_dep(name = "platforms", version = "0.0.4")
+bazel_dep(name = "bazel_skylib", version = "1.3.0")
+
+# Those are loaded only when using py_proto_library
+bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
+bazel_dep(name = "protobuf", version = "21.7", repo_name = "com_google_protobuf")
+
+internal_deps = use_extension("@rules_python//python/extensions/private:internal_deps.bzl", "internal_deps")
+internal_deps.install()
+use_repo(
+ internal_deps,
+ "pypi__build",
+ "pypi__click",
+ "pypi__colorama",
+ "pypi__importlib_metadata",
+ "pypi__installer",
+ "pypi__more_itertools",
+ "pypi__packaging",
+ "pypi__pep517",
+ "pypi__pip",
+ "pypi__pip_tools",
+ "pypi__setuptools",
+ "pypi__tomli",
+ "pypi__wheel",
+ "pypi__zipp",
+)
+
+# We need to do another use_extension call to expose the "pythons_hub"
+# repo.
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
+
+# The default toolchain to use if nobody configures a toolchain.
+# NOTE: This is not a stable version. It is provided for convenience, but will
+# change frequently to track the most recent Python version.
+# NOTE: The root module can override this.
+python.toolchain(
+ is_default = True,
+ python_version = "3.11",
+)
+use_repo(python, "pythons_hub")
+
+# This call registers the Python toolchains.
+register_toolchains("@pythons_hub//:all")
diff --git a/MODULE_LICENSE_APACHE2 b/MODULE_LICENSE_APACHE2
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/MODULE_LICENSE_APACHE2
diff --git a/OWNERS b/OWNERS
new file mode 100644
index 0000000..1ee860c
--- /dev/null
+++ b/OWNERS
@@ -0,0 +1,2 @@
+include platform/build/soong:/OWNERS
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..69be729
--- /dev/null
+++ b/README.md
@@ -0,0 +1,346 @@
+# Python Rules for Bazel
+
+* Postsubmit [![Build status](https://badge.buildkite.com/0bcfe58b6f5741aacb09b12485969ba7a1205955a45b53e854.svg?branch=main)](https://buildkite.com/bazel/python-rules-python-postsubmit)
+* Postsubmit + Current Bazel Incompatible Flags [![Build status](https://badge.buildkite.com/219007166ab6a7798b22758e7ae3f3223001398ffb56a5ad2a.svg?branch=main)](https://buildkite.com/bazel/rules-python-plus-bazelisk-migrate)
+
+## Overview
+
+This repository is the home of the core Python rules -- `py_library`,
+`py_binary`, `py_test`, `py_proto_library`, and related symbols that provide the basis for Python
+support in Bazel. It also contains package installation rules for integrating with PyPI and other package indices. Documentation lives in the
+[`docs/`](https://github.com/bazelbuild/rules_python/tree/main/docs)
+directory and in the
+[Bazel Build Encyclopedia](https://docs.bazel.build/versions/master/be/python.html).
+
+Currently the core rules are bundled with Bazel itself, and the symbols in this
+repository are simple aliases. However, in the future the rules will be
+migrated to Starlark and debundled from Bazel. Therefore, the future-proof way
+to depend on Python rules is via this repository. See[`Migrating from the Bundled Rules`](#Migrating-from-the-bundled-rules) below.
+
+The core rules are stable. Their implementation in Bazel is subject to Bazel's
+[backward compatibility policy](https://docs.bazel.build/versions/master/backward-compatibility.html).
+Once they are fully migrated to rules_python, they may evolve at a different
+rate, but this repository will still follow
+[semantic versioning](https://semver.org).
+
+The package installation rules (`pip_install`, `pip_parse` etc.) are less stable. We may make breaking
+changes as they evolve.
+
+This repository is maintained by the Bazel community. Neither Google, nor the
+Bazel team, provides support for the code. However, this repository is part of
+the test suite used to vet new Bazel releases. See the [How to
+contribute](CONTRIBUTING.md) page for information on our development workflow.
+
+## `bzlmod` support
+
+- Status: Beta
+- Full Feature Parity: No
+
+See [Bzlmod support](BZLMOD_SUPPORT.md) for more details.
+
+## Getting started
+
+The next two sections cover using `rules_python` with bzlmod and
+the older way of configuring bazel with a `WORKSPACE` file.
+
+### Using bzlmod
+
+NOTE: bzlmod support is still experimental; APIs subject to change.
+
+To import rules_python in your project, you first need to add it to your
+`MODULE.bazel` file, using the snippet provided in the
+[release you choose](https://github.com/bazelbuild/rules_python/releases).
+
+Once the dependency is added, a Python toolchain will be automatically
+registered and you'll be able to create runnable programs and tests.
+
+
+#### Toolchain registration with bzlmod
+
+NOTE: bzlmod support is still experimental; APIs subject to change.
+
+A default toolchain is automatically configured for by depending on
+`rules_python`. Note, however, the version used tracks the most recent Python
+release and will change often.
+
+If you want to register specific Python versions, then use
+`python.toolchain()` for each version you need:
+
+```starlark
+python = use_extension("@rules_python//python:extensions.bzl", "python")
+
+python.toolchain(
+ python_version = "3.9",
+)
+```
+
+### Using pip with bzlmod
+
+NOTE: bzlmod support is still experimental; APIs subject to change.
+
+To use dependencies from PyPI, the `pip.parse()` extension is used to
+convert a requirements file into Bazel dependencies.
+
+```starlark
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
+python.toolchain(
+ python_version = "3.9",
+)
+
+interpreter = use_extension("@rules_python//python/extensions:interpreter.bzl", "interpreter")
+interpreter.install(
+ name = "interpreter",
+ python_name = "python_3_9",
+)
+use_repo(interpreter, "interpreter")
+
+pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
+pip.parse(
+ hub_name = "pip",
+ python_interpreter_target = "@interpreter//:python",
+ requirements_lock = "//:requirements_lock.txt",
+ requirements_windows = "//:requirements_windows.txt",
+)
+use_repo(pip, "pip")
+```
+
+For more documentation see the bzlmod examples under the [examples](examples) folder.
+
+### Using a WORKSPACE file
+
+To import rules_python in your project, you first need to add it to your
+`WORKSPACE` file, using the snippet provided in the
+[release you choose](https://github.com/bazelbuild/rules_python/releases)
+
+To depend on a particular unreleased version, you can do:
+
+```python
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+rules_python_version = "740825b7f74930c62f44af95c9a4c1bd428d2c53" # Latest @ 2021-06-23
+
+http_archive(
+ name = "rules_python",
+ # Bazel will print the proper value to add here during the first build.
+ # sha256 = "FIXME",
+ strip_prefix = "rules_python-{}".format(rules_python_version),
+ url = "https://github.com/bazelbuild/rules_python/archive/{}.zip".format(rules_python_version),
+)
+```
+
+#### Toolchain registration
+
+To register a hermetic Python toolchain rather than rely on a system-installed interpreter for runtime execution, you can add to the `WORKSPACE` file:
+
+```python
+load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+
+python_register_toolchains(
+ name = "python3_9",
+ # Available versions are listed in @rules_python//python:versions.bzl.
+ # We recommend using the same version your team is already standardized on.
+ python_version = "3.9",
+)
+
+load("@python3_9//:defs.bzl", "interpreter")
+
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+pip_parse(
+ ...
+ python_interpreter_target = interpreter,
+ ...
+)
+```
+
+After registration, your Python targets will use the toolchain's interpreter during execution, but a system-installed interpreter
+is still used to 'bootstrap' Python targets (see https://github.com/bazelbuild/rules_python/issues/691).
+You may also find some quirks while using this toolchain. Please refer to [python-build-standalone documentation's _Quirks_ section](https://python-build-standalone.readthedocs.io/en/latest/quirks.html) for details.
+
+### Toolchain usage in other rules
+
+Python toolchains can be utilised in other bazel rules, such as `genrule()`, by adding the `toolchains=["@rules_python//python:current_py_toolchain"]` attribute. The path to the python interpreter can be obtained by using the `$(PYTHON2)` and `$(PYTHON3)` ["Make" Variables](https://bazel.build/reference/be/make-variables). See the [`test_current_py_toolchain`](tests/load_from_macro/BUILD.bazel) target for an example.
+
+
+### "Hello World"
+
+Once you've imported the rule set into your `WORKSPACE` using any of these
+methods, you can then load the core rules in your `BUILD` files with:
+
+``` python
+load("@rules_python//python:defs.bzl", "py_binary")
+
+py_binary(
+ name = "main",
+ srcs = ["main.py"],
+)
+```
+
+## Using the package installation rules
+
+Usage of the packaging rules involves two main steps.
+
+1. [Installing third_party packages](#installing-third_party-packages)
+2. [Using third_party packages as dependencies](#using-third_party-packages-as-dependencies)
+
+The package installation rules create two kinds of repositories: A central external repo that holds
+downloaded wheel files, and individual external repos for each wheel's extracted
+contents. Users only need to interact with the central external repo; the wheel repos
+are essentially an implementation detail. The central external repo provides a
+`WORKSPACE` macro to create the wheel repos, as well as a function, `requirement()`, for use in
+`BUILD` files that translates a pip package name into the label of a `py_library`
+target in the appropriate wheel repo.
+
+### Installing third_party packages
+
+#### Using bzlmod
+
+To add pip dependencies to your `MODULE.bazel` file, use the `pip.parse` extension, and call it to create the
+central external repo and individual wheel external repos.
+
+```python
+pip.parse(
+ hub_name = "my_deps",
+ requirements_lock = "//:requirements_lock.txt",
+)
+
+use_repo(pip, "my_deps")
+```
+
+#### Using a WORKSPACE file
+
+To add pip dependencies to your `WORKSPACE`, load the `pip_parse` function, and call it to create the
+central external repo and individual wheel external repos.
+
+
+```python
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+# Create a central repo that knows about the dependencies needed from
+# requirements_lock.txt.
+pip_parse(
+ name = "my_deps",
+ requirements_lock = "//path/to:requirements_lock.txt",
+)
+# Load the starlark macro which will define your dependencies.
+load("@my_deps//:requirements.bzl", "install_deps")
+# Call it to define repos for your requirements.
+install_deps()
+```
+
+#### pip rules
+
+Note that since `pip_parse` is a repository rule and therefore executes pip at WORKSPACE-evaluation time, Bazel has no
+information about the Python toolchain and cannot enforce that the interpreter
+used to invoke pip matches the interpreter used to run `py_binary` targets. By
+default, `pip_parse` uses the system command `"python3"`. This can be overridden by passing the
+`python_interpreter` attribute or `python_interpreter_target` attribute to `pip_parse`.
+
+You can have multiple `pip_parse`s in the same workspace. This will create multiple external repos that have no relation to one another, and may result in downloading the same wheels multiple times.
+
+As with any repository rule, if you would like to ensure that `pip_parse` is
+re-executed in order to pick up a non-hermetic change to your environment (e.g.,
+updating your system `python` interpreter), you can force it to re-execute by running
+`bazel sync --only [pip_parse name]`.
+
+Note: The `pip_install` rule is deprecated. `pip_parse` offers identical functionality and both `pip_install`
+and `pip_parse` now have the same implementation. The name `pip_install` may be removed in a future version of the rules.
+The maintainers have taken all reasonable efforts to faciliate a smooth transition, but some users of `pip_install` will
+need to replace their existing `requirements.txt` with a fully resolved set of dependencies using a tool such as
+`pip-tools` or the `compile_pip_requirements` repository rule.
+
+### Using third_party packages as dependencies
+
+Each extracted wheel repo contains a `py_library` target representing
+the wheel's contents. There are two ways to access this library. The
+first is using the `requirement()` function defined in the central
+repo's `//:requirements.bzl` file. This function maps a pip package
+name to a label:
+
+```python
+load("@my_deps//:requirements.bzl", "requirement")
+
+py_library(
+ name = "mylib",
+ srcs = ["mylib.py"],
+ deps = [
+ ":myotherlib",
+ requirement("some_pip_dep"),
+ requirement("another_pip_dep"),
+ ]
+)
+```
+
+The reason `requirement()` exists is that the pattern for the labels,
+while not expected to change frequently, is not guaranteed to be
+stable. Using `requirement()` ensures that you do not have to refactor
+your `BUILD` files if the pattern changes.
+
+On the other hand, using `requirement()` has several drawbacks; see
+[this issue][requirements-drawbacks] for an enumeration. If you don't
+want to use `requirement()` then you can instead use the library
+labels directly. For `pip_parse` the labels are of the form
+
+```
+@{name}_{package}//:pkg
+```
+
+Here `name` is the `name` attribute that was passed to `pip_parse` and
+`package` is the pip package name with characters that are illegal in
+Bazel label names (e.g. `-`, `.`) replaced with `_`. If you need to
+update `name` from "old" to "new", then you can run the following
+buildozer command:
+
+```
+buildozer 'substitute deps @old_([^/]+)//:pkg @new_${1}//:pkg' //...:*
+```
+
+For `pip_install` the labels are instead of the form
+
+```
+@{name}//pypi__{package}
+```
+
+[requirements-drawbacks]: https://github.com/bazelbuild/rules_python/issues/414
+
+#### 'Extras' dependencies
+
+Any 'extras' specified in the requirements lock-file will be automatically added as transitive dependencies of the
+package. In the example above, you'd just put `requirement("useful_dep")`.
+
+### Consuming Wheel Dists Directly
+
+If you need to depend on the wheel dists themselves, for instance to pass them
+to some other packaging tool, you can get a handle to them with the `whl_requirement` macro. For example:
+
+```python
+filegroup(
+ name = "whl_files",
+ data = [
+ whl_requirement("boto3"),
+ ]
+)
+```
+
+## Migrating from the bundled rules
+
+The core rules are currently available in Bazel as built-in symbols, but this
+form is deprecated. Instead, you should depend on rules_python in your
+`WORKSPACE` file and load the Python rules from
+`@rules_python//python:defs.bzl`.
+
+A [buildifier](https://github.com/bazelbuild/buildtools/blob/master/buildifier/README.md)
+fix is available to automatically migrate `BUILD` and `.bzl` files to add the
+appropriate `load()` statements and rewrite uses of `native.py_*`.
+
+```sh
+# Also consider using the -r flag to modify an entire workspace.
+buildifier --lint=fix --warnings=native-py <files>
+```
+
+Currently the `WORKSPACE` file needs to be updated manually as per [Getting
+started](#Getting-started) above.
+
+Note that Starlark-defined bundled symbols underneath
+`@bazel_tools//tools/python` are also deprecated. These are not yet rewritten
+by buildifier.
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..a833de8
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,88 @@
+# Copyright 2017 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.
+
+workspace(name = "rules_python")
+
+# Everything below this line is used only for developing rules_python. Users
+# should not copy it to their WORKSPACE.
+
+load("//:internal_deps.bzl", "rules_python_internal_deps")
+
+rules_python_internal_deps()
+
+load("//:internal_setup.bzl", "rules_python_internal_setup")
+
+rules_python_internal_setup()
+
+load("//python:repositories.bzl", "python_register_multi_toolchains")
+load("//python:versions.bzl", "MINOR_MAPPING")
+
+python_register_multi_toolchains(
+ name = "python",
+ default_version = MINOR_MAPPING.values()[-1],
+ python_versions = MINOR_MAPPING.values(),
+)
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+# Used for Bazel CI
+http_archive(
+ name = "bazelci_rules",
+ sha256 = "eca21884e6f66a88c358e580fd67a6b148d30ab57b1680f62a96c00f9bc6a07e",
+ strip_prefix = "bazelci_rules-1.0.0",
+ url = "https://github.com/bazelbuild/continuous-integration/releases/download/rules-1.0.0/bazelci_rules-1.0.0.tar.gz",
+)
+
+load("@bazelci_rules//:rbe_repo.bzl", "rbe_preconfig")
+
+# Creates a default toolchain config for RBE.
+# Use this as is if you are using the rbe_ubuntu16_04 container,
+# otherwise refer to RBE docs.
+rbe_preconfig(
+ name = "buildkite_config",
+ toolchain = "ubuntu1804-bazel-java11",
+)
+
+local_repository(
+ name = "rules_python_gazelle_plugin",
+ path = "gazelle",
+)
+
+# The rules_python gazelle extension has some third-party go dependencies
+# which we need to fetch in order to compile it.
+load("@rules_python_gazelle_plugin//:deps.bzl", _py_gazelle_deps = "gazelle_deps")
+
+# See: https://github.com/bazelbuild/rules_python/blob/main/gazelle/README.md
+# This rule loads and compiles various go dependencies that running gazelle
+# for python requirements.
+_py_gazelle_deps()
+
+#####################
+# Install twine for our own runfiles wheel publishing.
+# Eventually we might want to install twine automatically for users too, see:
+# https://github.com/bazelbuild/rules_python/issues/1016.
+load("@python//3.11.1:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+pip_parse(
+ name = "publish_deps",
+ python_interpreter_target = interpreter,
+ requirements_darwin = "//tools/publish:requirements_darwin.txt",
+ requirements_lock = "//tools/publish:requirements.txt",
+ requirements_windows = "//tools/publish:requirements_windows.txt",
+)
+
+load("@publish_deps//:requirements.bzl", "install_deps")
+
+install_deps()
diff --git a/addlicense.sh b/addlicense.sh
new file mode 100755
index 0000000..8cc8fb3
--- /dev/null
+++ b/addlicense.sh
@@ -0,0 +1,23 @@
+#!/bin/bash
+# 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.
+
+
+if ! command -v addlicense @>&1 >/dev/null; then
+ echo "ERROR: addlicense not installed."
+ echo "Install using https://github.com/google/addlicense#install"
+ exit 1
+fi
+
+addlicense -v -l apache -c 'The Bazel Authors. All rights reserved.' "$@"
diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel
new file mode 100644
index 0000000..1fb4f81
--- /dev/null
+++ b/docs/BUILD.bazel
@@ -0,0 +1,187 @@
+# Copyright 2017 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//:bzl_library.bzl", "bzl_library")
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+load("@io_bazel_stardoc//stardoc:stardoc.bzl", "stardoc")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache 2.0
+
+_DOCS = {
+ "packaging": "//docs:packaging-docs",
+ "pip": "//docs:pip-docs",
+ "pip_repository": "//docs:pip-repository",
+ "py_cc_toolchain": "//docs:py_cc_toolchain-docs",
+ "py_cc_toolchain_info": "//docs:py_cc_toolchain_info-docs",
+ "python": "//docs:core-docs",
+}
+
+# We define these bzl_library targets here rather than in the //python package
+# because they're only used for doc generation. This way, we avoid requiring
+# our users to depend on Skylib.
+
+bzl_library(
+ name = "bazel_repo_tools",
+ srcs = [
+ "@bazel_tools//tools:bzl_srcs",
+ ],
+)
+
+bzl_library(
+ name = "defs",
+ srcs = [
+ "//python:defs.bzl",
+ "//python/private:reexports.bzl",
+ ],
+ deps = [
+ ":bazel_repo_tools",
+ "//python:defs_bzl",
+ "//python/private:reexports_bzl",
+ ],
+)
+
+bzl_library(
+ name = "pip_install_bzl",
+ srcs = [
+ "//python:bzl",
+ "//python/pip_install:bzl",
+ ],
+ deps = [
+ ":defs",
+ "//:version.bzl",
+ ],
+)
+
+bzl_library(
+ name = "requirements_parser_bzl",
+ srcs = [
+ "//python/pip_install:requirements_parser.bzl",
+ ],
+)
+
+bzl_library(
+ name = "packaging_bzl",
+ srcs = [
+ "//python:packaging.bzl",
+ "//python/private:py_package.bzl",
+ "//python/private:py_wheel.bzl",
+ "//python/private:stamp.bzl",
+ "//python/private:util.bzl",
+ ],
+ deps = [
+ "//python/private:util_bzl",
+ ],
+)
+
+# TODO: Stardoc does not guarantee consistent outputs accross platforms (Unix/Windows).
+# As a result we do not build or test docs on Windows.
+_NOT_WINDOWS = select({
+ "@platforms//os:linux": [],
+ "@platforms//os:macos": [],
+ "//conditions:default": ["@platforms//:incompatible"],
+})
+
+stardoc(
+ name = "core-docs",
+ out = "python.md_",
+ input = "//python:defs.bzl",
+ target_compatible_with = _NOT_WINDOWS,
+ deps = [":defs"],
+)
+
+stardoc(
+ name = "pip-docs",
+ out = "pip.md_",
+ input = "//python:pip.bzl",
+ target_compatible_with = _NOT_WINDOWS,
+ deps = [
+ ":bazel_repo_tools",
+ ":pip_install_bzl",
+ "@bazel_skylib//lib:versions",
+ ],
+)
+
+stardoc(
+ name = "pip-repository",
+ out = "pip_repository.md_",
+ input = "//python/pip_install:pip_repository.bzl",
+ target_compatible_with = _NOT_WINDOWS,
+ deps = [
+ ":bazel_repo_tools",
+ ":pip_install_bzl",
+ ":requirements_parser_bzl",
+ "@bazel_skylib//lib:versions",
+ ],
+)
+
+stardoc(
+ name = "packaging-docs",
+ out = "packaging.md_",
+ input = "//python:packaging.bzl",
+ target_compatible_with = _NOT_WINDOWS,
+ deps = [":packaging_bzl"],
+)
+
+stardoc(
+ name = "py_cc_toolchain-docs",
+ out = "py_cc_toolchain.md_",
+ # NOTE: The public file isn't used as the input because it would document
+ # the macro, which doesn't have the attribute documentation. The macro
+ # doesn't do anything interesting to users, so bypass it to avoid having to
+ # copy/paste all the rule's doc in the macro.
+ input = "//python/private:py_cc_toolchain_rule.bzl",
+ target_compatible_with = _NOT_WINDOWS,
+ deps = ["//python/private:py_cc_toolchain_bzl"],
+)
+
+stardoc(
+ name = "py_cc_toolchain_info-docs",
+ out = "py_cc_toolchain_info.md_",
+ input = "//python/cc:py_cc_toolchain_info.bzl",
+ deps = ["//python/cc:py_cc_toolchain_info_bzl"],
+)
+
+[
+ diff_test(
+ name = "check_" + k,
+ failure_message = "Please run: bazel run //docs:update",
+ file1 = k + ".md",
+ file2 = k + ".md_",
+ target_compatible_with = _NOT_WINDOWS,
+ )
+ for k in _DOCS.keys()
+]
+
+write_file(
+ name = "gen_update",
+ out = "update.sh",
+ content = [
+ "#!/usr/bin/env bash",
+ "cd $BUILD_WORKSPACE_DIRECTORY",
+ ] + [
+ "cp -fv bazel-bin/docs/{0}.md_ docs/{0}.md".format(k)
+ for k in _DOCS.keys()
+ ],
+ target_compatible_with = _NOT_WINDOWS,
+)
+
+sh_binary(
+ name = "update",
+ srcs = ["update.sh"],
+ data = _DOCS.values(),
+ target_compatible_with = _NOT_WINDOWS,
+)
diff --git a/docs/coverage.md b/docs/coverage.md
new file mode 100644
index 0000000..63f2578
--- /dev/null
+++ b/docs/coverage.md
@@ -0,0 +1,58 @@
+# Setting up coverage
+
+As of Bazel 6, the Python toolchains and bootstrap logic supports providing
+coverage information using the `coverage` library.
+
+As of `rules_python` version `0.18.1`, builtin coverage support can be enabled
+when configuring toolchains.
+
+## Enabling `rules_python` coverage support
+
+Enabling the coverage support bundled with `rules_python` just requires setting an
+argument when registerting toolchains.
+
+For Bzlmod:
+
+```starlark
+python.toolchain(
+ "@python3_9_toolchains//:all",
+ configure_coverage_tool = True,
+)
+```
+
+For WORKSPACE configuration:
+
+```starlark
+python_register_toolchains(
+ register_coverage_tool = True,
+)
+```
+
+NOTE: This will implicitly add the version of `coverage` bundled with
+`rules_python` to the dependencies of `py_test` rules when `bazel coverage` is
+run. If a target already transitively depends on a different version of
+`coverage`, then behavior is undefined -- it is undefined which version comes
+first in the import path. If you find yourself in this situation, then you'll
+need to manually configure coverage (see below).
+
+## Manually configuring coverage
+
+To manually configure coverage support, you'll need to set the
+`py_runtime.coverage_tool` attribute. This attribute is a target that specifies
+the coverage entry point file and, optionally, client libraries that are added
+to `py_test` targets. Typically, this would be a `filegroup` that looked like:
+
+```starlark
+filegroup(
+ name = "coverage",
+ srcs = ["coverage_main.py"],
+ data = ["coverage_lib1.py", ...]
+)
+```
+
+Using `filegroup` isn't required, nor are including client libraries. The
+important behaviors of the target are:
+
+* It provides a single output file OR it provides an executable output; this
+ output is treated as the coverage entry point.
+* If it provides runfiles, then `runfiles.files` are included into `py_test`.
diff --git a/docs/packaging.md b/docs/packaging.md
new file mode 100755
index 0000000..0e8e110
--- /dev/null
+++ b/docs/packaging.md
@@ -0,0 +1,211 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Public API for for building wheels.
+
+<a id="py_package"></a>
+
+## py_package
+
+<pre>
+py_package(<a href="#py_package-name">name</a>, <a href="#py_package-deps">deps</a>, <a href="#py_package-packages">packages</a>)
+</pre>
+
+A rule to select all files in transitive dependencies of deps which
+belong to given set of Python packages.
+
+This rule is intended to be used as data dependency to py_wheel rule.
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="py_package-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="py_package-deps"></a>deps | - | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
+| <a id="py_package-packages"></a>packages | List of Python packages to include in the distribution. Sub-packages are automatically included. | List of strings | optional | <code>[]</code> |
+
+
+<a id="py_wheel_dist"></a>
+
+## py_wheel_dist
+
+<pre>
+py_wheel_dist(<a href="#py_wheel_dist-name">name</a>, <a href="#py_wheel_dist-out">out</a>, <a href="#py_wheel_dist-wheel">wheel</a>)
+</pre>
+
+Prepare a dist/ folder, following Python's packaging standard practice.
+
+See https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives
+which recommends a dist/ folder containing the wheel file(s), source distributions, etc.
+
+This also has the advantage that stamping information is included in the wheel's filename.
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="py_wheel_dist-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="py_wheel_dist-out"></a>out | name of the resulting directory | String | required | |
+| <a id="py_wheel_dist-wheel"></a>wheel | a [py_wheel rule](/docs/packaging.md#py_wheel_rule) | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+
+
+<a id="py_wheel_rule"></a>
+
+## py_wheel_rule
+
+<pre>
+py_wheel_rule(<a href="#py_wheel_rule-name">name</a>, <a href="#py_wheel_rule-abi">abi</a>, <a href="#py_wheel_rule-author">author</a>, <a href="#py_wheel_rule-author_email">author_email</a>, <a href="#py_wheel_rule-classifiers">classifiers</a>, <a href="#py_wheel_rule-console_scripts">console_scripts</a>, <a href="#py_wheel_rule-deps">deps</a>,
+ <a href="#py_wheel_rule-description_content_type">description_content_type</a>, <a href="#py_wheel_rule-description_file">description_file</a>, <a href="#py_wheel_rule-distribution">distribution</a>, <a href="#py_wheel_rule-entry_points">entry_points</a>,
+ <a href="#py_wheel_rule-extra_distinfo_files">extra_distinfo_files</a>, <a href="#py_wheel_rule-extra_requires">extra_requires</a>, <a href="#py_wheel_rule-homepage">homepage</a>, <a href="#py_wheel_rule-license">license</a>, <a href="#py_wheel_rule-platform">platform</a>, <a href="#py_wheel_rule-project_urls">project_urls</a>,
+ <a href="#py_wheel_rule-python_requires">python_requires</a>, <a href="#py_wheel_rule-python_tag">python_tag</a>, <a href="#py_wheel_rule-requires">requires</a>, <a href="#py_wheel_rule-stamp">stamp</a>, <a href="#py_wheel_rule-strip_path_prefixes">strip_path_prefixes</a>, <a href="#py_wheel_rule-summary">summary</a>, <a href="#py_wheel_rule-version">version</a>)
+</pre>
+
+Internal rule used by the [py_wheel macro](/docs/packaging.md#py_wheel).
+
+These intentionally have the same name to avoid sharp edges with Bazel macros.
+For example, a `bazel query` for a user's `py_wheel` macro expands to `py_wheel` targets,
+in the way they expect.
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="py_wheel_rule-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="py_wheel_rule-abi"></a>abi | Python ABI tag. 'none' for pure-Python wheels. | String | optional | <code>"none"</code> |
+| <a id="py_wheel_rule-author"></a>author | A string specifying the author of the package. | String | optional | <code>""</code> |
+| <a id="py_wheel_rule-author_email"></a>author_email | A string specifying the email address of the package author. | String | optional | <code>""</code> |
+| <a id="py_wheel_rule-classifiers"></a>classifiers | A list of strings describing the categories for the package. For valid classifiers see https://pypi.org/classifiers | List of strings | optional | <code>[]</code> |
+| <a id="py_wheel_rule-console_scripts"></a>console_scripts | Deprecated console_script entry points, e.g. <code>{'main': 'examples.wheel.main:main'}</code>.<br><br>Deprecated: prefer the <code>entry_points</code> attribute, which supports <code>console_scripts</code> as well as other entry points. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
+| <a id="py_wheel_rule-deps"></a>deps | Targets to be included in the distribution.<br><br>The targets to package are usually <code>py_library</code> rules or filesets (for packaging data files).<br><br>Note it's usually better to package <code>py_library</code> targets and use <code>entry_points</code> attribute to specify <code>console_scripts</code> than to package <code>py_binary</code> rules. <code>py_binary</code> targets would wrap a executable script that tries to locate <code>.runfiles</code> directory which is not packaged in the wheel. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
+| <a id="py_wheel_rule-description_content_type"></a>description_content_type | The type of contents in description_file. If not provided, the type will be inferred from the extension of description_file. Also see https://packaging.python.org/en/latest/specifications/core-metadata/#description-content-type | String | optional | <code>""</code> |
+| <a id="py_wheel_rule-description_file"></a>description_file | A file containing text describing the package. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="py_wheel_rule-distribution"></a>distribution | Name of the distribution.<br><br>This should match the project name onm PyPI. It's also the name that is used to refer to the package in other packages' dependencies.<br><br>Workspace status keys are expanded using <code>{NAME}</code> format, for example: - <code>distribution = "package.{CLASSIFIER}"</code> - <code>distribution = "{DISTRIBUTION}"</code><br><br>For the available keys, see https://bazel.build/docs/user-manual#workspace-status | String | required | |
+| <a id="py_wheel_rule-entry_points"></a>entry_points | entry_points, e.g. <code>{'console_scripts': ['main = examples.wheel.main:main']}</code>. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> List of strings</a> | optional | <code>{}</code> |
+| <a id="py_wheel_rule-extra_distinfo_files"></a>extra_distinfo_files | Extra files to add to distinfo directory in the archive. | <a href="https://bazel.build/rules/lib/dict">Dictionary: Label -> String</a> | optional | <code>{}</code> |
+| <a id="py_wheel_rule-extra_requires"></a>extra_requires | List of optional requirements for this package | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> List of strings</a> | optional | <code>{}</code> |
+| <a id="py_wheel_rule-homepage"></a>homepage | A string specifying the URL for the package homepage. | String | optional | <code>""</code> |
+| <a id="py_wheel_rule-license"></a>license | A string specifying the license of the package. | String | optional | <code>""</code> |
+| <a id="py_wheel_rule-platform"></a>platform | Supported platform. Use 'any' for pure-Python wheel.<br><br>If you have included platform-specific data, such as a .pyd or .so extension module, you will need to specify the platform in standard pip format. If you support multiple platforms, you can define platform constraints, then use a select() to specify the appropriate specifier, eg:<br><br><code> platform = select({ "//platforms:windows_x86_64": "win_amd64", "//platforms:macos_x86_64": "macosx_10_7_x86_64", "//platforms:linux_x86_64": "manylinux2014_x86_64", }) </code> | String | optional | <code>"any"</code> |
+| <a id="py_wheel_rule-project_urls"></a>project_urls | A string dict specifying additional browsable URLs for the project and corresponding labels, where label is the key and url is the value. e.g <code>{{"Bug Tracker": "http://bitbucket.org/tarek/distribute/issues/"}}</code> | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
+| <a id="py_wheel_rule-python_requires"></a>python_requires | Python versions required by this distribution, e.g. '&gt;=3.5,&lt;3.7' | String | optional | <code>""</code> |
+| <a id="py_wheel_rule-python_tag"></a>python_tag | Supported Python version(s), eg <code>py3</code>, <code>cp35.cp36</code>, etc | String | optional | <code>"py3"</code> |
+| <a id="py_wheel_rule-requires"></a>requires | List of requirements for this package. See the section on [Declaring required dependency](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#declaring-dependencies) for details and examples of the format of this argument. | List of strings | optional | <code>[]</code> |
+| <a id="py_wheel_rule-stamp"></a>stamp | Whether to encode build information into the wheel. Possible values:<br><br>- <code>stamp = 1</code>: Always stamp the build information into the wheel, even in [--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. This setting should be avoided, since it potentially kills remote caching for the target and any downstream actions that depend on it.<br><br>- <code>stamp = 0</code>: Always replace build information by constant values. This gives good build result caching.<br><br>- <code>stamp = -1</code>: Embedding of build information is controlled by the [--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.<br><br>Stamped targets are not rebuilt unless their dependencies change. | Integer | optional | <code>-1</code> |
+| <a id="py_wheel_rule-strip_path_prefixes"></a>strip_path_prefixes | path prefixes to strip from files added to the generated package | List of strings | optional | <code>[]</code> |
+| <a id="py_wheel_rule-summary"></a>summary | A one-line summary of what the distribution does | String | optional | <code>""</code> |
+| <a id="py_wheel_rule-version"></a>version | Version number of the package.<br><br>Note that this attribute supports stamp format strings as well as 'make variables'. For example: - <code>version = "1.2.3-{BUILD_TIMESTAMP}"</code> - <code>version = "{BUILD_EMBED_LABEL}"</code> - <code>version = "$(VERSION)"</code><br><br>Note that Bazel's output filename cannot include the stamp information, as outputs must be known during the analysis phase and the stamp data is available only during the action execution.<br><br>The [<code>py_wheel</code>](/docs/packaging.md#py_wheel) macro produces a <code>.dist</code>-suffix target which creates a <code>dist/</code> folder containing the wheel with the stamped name, suitable for publishing.<br><br>See [<code>py_wheel_dist</code>](/docs/packaging.md#py_wheel_dist) for more info. | String | required | |
+
+
+<a id="PyWheelInfo"></a>
+
+## PyWheelInfo
+
+<pre>
+PyWheelInfo(<a href="#PyWheelInfo-name_file">name_file</a>, <a href="#PyWheelInfo-wheel">wheel</a>)
+</pre>
+
+Information about a wheel produced by `py_wheel`
+
+**FIELDS**
+
+
+| Name | Description |
+| :------------- | :------------- |
+| <a id="PyWheelInfo-name_file"></a>name_file | File: A file containing the canonical name of the wheel (after stamping, if enabled). |
+| <a id="PyWheelInfo-wheel"></a>wheel | File: The wheel file itself. |
+
+
+<a id="py_wheel"></a>
+
+## py_wheel
+
+<pre>
+py_wheel(<a href="#py_wheel-name">name</a>, <a href="#py_wheel-twine">twine</a>, <a href="#py_wheel-publish_args">publish_args</a>, <a href="#py_wheel-kwargs">kwargs</a>)
+</pre>
+
+Builds a Python Wheel.
+
+Wheels are Python distribution format defined in https://www.python.org/dev/peps/pep-0427/.
+
+This macro packages a set of targets into a single wheel.
+It wraps the [py_wheel rule](#py_wheel_rule).
+
+Currently only pure-python wheels are supported.
+
+Examples:
+
+```python
+# Package some specific py_library targets, without their dependencies
+py_wheel(
+ name = "minimal_with_py_library",
+ # 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",
+ ],
+)
+
+# 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_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"],
+)
+```
+
+To publish the wheel to Pypi, the twine package is required.
+rules_python doesn't provide twine itself, see https://github.com/bazelbuild/rules_python/issues/1016
+However you can install it with pip_parse, just like we do in the WORKSPACE file in rules_python.
+
+Once you've installed twine, you can pass its label to the `twine` attribute of this macro,
+to get a "[name].publish" target.
+
+Example:
+
+```python
+py_wheel(
+ name = "my_wheel",
+ twine = "@publish_deps_twine//:pkg",
+ ...
+)
+```
+
+Now you can run a command like the following, which publishes to https://test.pypi.org/
+
+```sh
+% TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-*** \
+ bazel run --stamp --embed_label=1.2.4 -- \
+ //path/to:my_wheel.publish --repository testpypi
+```
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="py_wheel-name"></a>name | A unique name for this target. | none |
+| <a id="py_wheel-twine"></a>twine | A label of the external location of the py_library target for twine | <code>None</code> |
+| <a id="py_wheel-publish_args"></a>publish_args | arguments passed to twine, e.g. ["--repository-url", "https://pypi.my.org/simple/"]. These are subject to make var expansion, as with the <code>args</code> attribute. Note that you can also pass additional args to the bazel run command as in the example above. | <code>[]</code> |
+| <a id="py_wheel-kwargs"></a>kwargs | other named parameters passed to the underlying [py_wheel rule](#py_wheel_rule) | none |
+
+
diff --git a/docs/pip.md b/docs/pip.md
new file mode 100644
index 0000000..6b96607
--- /dev/null
+++ b/docs/pip.md
@@ -0,0 +1,273 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Import pip requirements into Bazel.
+
+<a id="whl_library_alias"></a>
+
+## whl_library_alias
+
+<pre>
+whl_library_alias(<a href="#whl_library_alias-name">name</a>, <a href="#whl_library_alias-default_version">default_version</a>, <a href="#whl_library_alias-repo_mapping">repo_mapping</a>, <a href="#whl_library_alias-version_map">version_map</a>, <a href="#whl_library_alias-wheel_name">wheel_name</a>)
+</pre>
+
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="whl_library_alias-name"></a>name | A unique name for this repository. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="whl_library_alias-default_version"></a>default_version | - | String | required | |
+| <a id="whl_library_alias-repo_mapping"></a>repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.&lt;p&gt;For example, an entry <code>"@foo": "@bar"</code> declares that, for any time this repository depends on <code>@foo</code> (such as a dependency on <code>@foo//some:target</code>, it should actually resolve that dependency within globally-declared <code>@bar</code> (<code>@bar//some:target</code>). | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | required | |
+| <a id="whl_library_alias-version_map"></a>version_map | - | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | required | |
+| <a id="whl_library_alias-wheel_name"></a>wheel_name | - | String | required | |
+
+
+<a id="compile_pip_requirements"></a>
+
+## compile_pip_requirements
+
+<pre>
+compile_pip_requirements(<a href="#compile_pip_requirements-name">name</a>, <a href="#compile_pip_requirements-extra_args">extra_args</a>, <a href="#compile_pip_requirements-extra_deps">extra_deps</a>, <a href="#compile_pip_requirements-generate_hashes">generate_hashes</a>, <a href="#compile_pip_requirements-py_binary">py_binary</a>, <a href="#compile_pip_requirements-py_test">py_test</a>,
+ <a href="#compile_pip_requirements-requirements_in">requirements_in</a>, <a href="#compile_pip_requirements-requirements_txt">requirements_txt</a>, <a href="#compile_pip_requirements-requirements_darwin">requirements_darwin</a>, <a href="#compile_pip_requirements-requirements_linux">requirements_linux</a>,
+ <a href="#compile_pip_requirements-requirements_windows">requirements_windows</a>, <a href="#compile_pip_requirements-visibility">visibility</a>, <a href="#compile_pip_requirements-tags">tags</a>, <a href="#compile_pip_requirements-kwargs">kwargs</a>)
+</pre>
+
+Generates targets for managing pip dependencies with pip-compile.
+
+By default this rules generates a filegroup named "[name]" which can be included in the data
+of some other compile_pip_requirements rule that references these requirements
+(e.g. with `-r ../other/requirements.txt`).
+
+It also generates two targets for running pip-compile:
+
+- validate with `bazel test [name]_test`
+- update with `bazel run [name].update`
+
+If you are using a version control system, the requirements.txt generated by this rule should
+be checked into it to ensure that all developers/users have the same dependency versions.
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="compile_pip_requirements-name"></a>name | base name for generated targets, typically "requirements". | none |
+| <a id="compile_pip_requirements-extra_args"></a>extra_args | passed to pip-compile. | <code>[]</code> |
+| <a id="compile_pip_requirements-extra_deps"></a>extra_deps | extra dependencies passed to pip-compile. | <code>[]</code> |
+| <a id="compile_pip_requirements-generate_hashes"></a>generate_hashes | whether to put hashes in the requirements_txt file. | <code>True</code> |
+| <a id="compile_pip_requirements-py_binary"></a>py_binary | the py_binary rule to be used. | <code>&lt;function py_binary&gt;</code> |
+| <a id="compile_pip_requirements-py_test"></a>py_test | the py_test rule to be used. | <code>&lt;function py_test&gt;</code> |
+| <a id="compile_pip_requirements-requirements_in"></a>requirements_in | file expressing desired dependencies. | <code>None</code> |
+| <a id="compile_pip_requirements-requirements_txt"></a>requirements_txt | result of "compiling" the requirements.in file. | <code>None</code> |
+| <a id="compile_pip_requirements-requirements_darwin"></a>requirements_darwin | File of darwin specific resolve output to check validate if requirement.in has changes. | <code>None</code> |
+| <a id="compile_pip_requirements-requirements_linux"></a>requirements_linux | File of linux specific resolve output to check validate if requirement.in has changes. | <code>None</code> |
+| <a id="compile_pip_requirements-requirements_windows"></a>requirements_windows | File of windows specific resolve output to check validate if requirement.in has changes. | <code>None</code> |
+| <a id="compile_pip_requirements-visibility"></a>visibility | passed to both the _test and .update rules. | <code>["//visibility:private"]</code> |
+| <a id="compile_pip_requirements-tags"></a>tags | tagging attribute common to all build rules, passed to both the _test and .update rules. | <code>None</code> |
+| <a id="compile_pip_requirements-kwargs"></a>kwargs | other bazel attributes passed to the "_test" rule. | none |
+
+
+<a id="multi_pip_parse"></a>
+
+## multi_pip_parse
+
+<pre>
+multi_pip_parse(<a href="#multi_pip_parse-name">name</a>, <a href="#multi_pip_parse-default_version">default_version</a>, <a href="#multi_pip_parse-python_versions">python_versions</a>, <a href="#multi_pip_parse-python_interpreter_target">python_interpreter_target</a>,
+ <a href="#multi_pip_parse-requirements_lock">requirements_lock</a>, <a href="#multi_pip_parse-kwargs">kwargs</a>)
+</pre>
+
+NOT INTENDED FOR DIRECT USE!
+
+This is intended to be used by the multi_pip_parse implementation in the template of the
+multi_toolchain_aliases repository rule.
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="multi_pip_parse-name"></a>name | the name of the multi_pip_parse repository. | none |
+| <a id="multi_pip_parse-default_version"></a>default_version | the default Python version. | none |
+| <a id="multi_pip_parse-python_versions"></a>python_versions | all Python toolchain versions currently registered. | none |
+| <a id="multi_pip_parse-python_interpreter_target"></a>python_interpreter_target | a dictionary which keys are Python versions and values are resolved host interpreters. | none |
+| <a id="multi_pip_parse-requirements_lock"></a>requirements_lock | a dictionary which keys are Python versions and values are locked requirements files. | none |
+| <a id="multi_pip_parse-kwargs"></a>kwargs | extra arguments passed to all wrapped pip_parse. | none |
+
+**RETURNS**
+
+The internal implementation of multi_pip_parse repository rule.
+
+
+<a id="package_annotation"></a>
+
+## package_annotation
+
+<pre>
+package_annotation(<a href="#package_annotation-additive_build_content">additive_build_content</a>, <a href="#package_annotation-copy_files">copy_files</a>, <a href="#package_annotation-copy_executables">copy_executables</a>, <a href="#package_annotation-data">data</a>, <a href="#package_annotation-data_exclude_glob">data_exclude_glob</a>,
+ <a href="#package_annotation-srcs_exclude_glob">srcs_exclude_glob</a>)
+</pre>
+
+Annotations to apply to the BUILD file content from package generated from a `pip_repository` rule.
+
+[cf]: https://github.com/bazelbuild/bazel-skylib/blob/main/docs/copy_file_doc.md
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="package_annotation-additive_build_content"></a>additive_build_content | Raw text to add to the generated <code>BUILD</code> file of a package. | <code>None</code> |
+| <a id="package_annotation-copy_files"></a>copy_files | A mapping of <code>src</code> and <code>out</code> files for [@bazel_skylib//rules:copy_file.bzl][cf] | <code>{}</code> |
+| <a id="package_annotation-copy_executables"></a>copy_executables | A mapping of <code>src</code> and <code>out</code> files for [@bazel_skylib//rules:copy_file.bzl][cf]. Targets generated here will also be flagged as executable. | <code>{}</code> |
+| <a id="package_annotation-data"></a>data | A list of labels to add as <code>data</code> dependencies to the generated <code>py_library</code> target. | <code>[]</code> |
+| <a id="package_annotation-data_exclude_glob"></a>data_exclude_glob | A list of exclude glob patterns to add as <code>data</code> to the generated <code>py_library</code> target. | <code>[]</code> |
+| <a id="package_annotation-srcs_exclude_glob"></a>srcs_exclude_glob | A list of labels to add as <code>srcs</code> to the generated <code>py_library</code> target. | <code>[]</code> |
+
+**RETURNS**
+
+str: A json encoded string of the provided content.
+
+
+<a id="pip_install"></a>
+
+## pip_install
+
+<pre>
+pip_install(<a href="#pip_install-requirements">requirements</a>, <a href="#pip_install-name">name</a>, <a href="#pip_install-kwargs">kwargs</a>)
+</pre>
+
+Accepts a locked/compiled requirements file and installs the dependencies listed within.
+
+```python
+load("@rules_python//python:pip.bzl", "pip_install")
+
+pip_install(
+ name = "pip_deps",
+ requirements = ":requirements.txt",
+)
+
+load("@pip_deps//:requirements.bzl", "install_deps")
+
+install_deps()
+```
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="pip_install-requirements"></a>requirements | A 'requirements.txt' pip requirements file. | <code>None</code> |
+| <a id="pip_install-name"></a>name | A unique name for the created external repository (default 'pip'). | <code>"pip"</code> |
+| <a id="pip_install-kwargs"></a>kwargs | Additional arguments to the [<code>pip_repository</code>](./pip_repository.md) repository rule. | none |
+
+
+<a id="pip_parse"></a>
+
+## pip_parse
+
+<pre>
+pip_parse(<a href="#pip_parse-requirements">requirements</a>, <a href="#pip_parse-requirements_lock">requirements_lock</a>, <a href="#pip_parse-name">name</a>, <a href="#pip_parse-kwargs">kwargs</a>)
+</pre>
+
+Accepts a locked/compiled requirements file and installs the dependencies listed within.
+
+Those dependencies become available in a generated `requirements.bzl` file.
+You can instead check this `requirements.bzl` file into your repo, see the "vendoring" section below.
+
+This macro wraps the [`pip_repository`](./pip_repository.md) rule that invokes `pip`.
+In your WORKSPACE file:
+
+```python
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+pip_parse(
+ name = "pip_deps",
+ requirements_lock = ":requirements.txt",
+)
+
+load("@pip_deps//:requirements.bzl", "install_deps")
+
+install_deps()
+```
+
+You can then reference installed dependencies from a `BUILD` file with:
+
+```python
+load("@pip_deps//:requirements.bzl", "requirement")
+
+py_library(
+ name = "bar",
+ ...
+ deps = [
+ "//my/other:dep",
+ requirement("requests"),
+ requirement("numpy"),
+ ],
+)
+```
+
+In addition to the `requirement` macro, which is used to access the generated `py_library`
+target generated from a package's wheel, The generated `requirements.bzl` file contains
+functionality for exposing [entry points][whl_ep] as `py_binary` targets as well.
+
+[whl_ep]: https://packaging.python.org/specifications/entry-points/
+
+```python
+load("@pip_deps//:requirements.bzl", "entry_point")
+
+alias(
+ name = "pip-compile",
+ actual = entry_point(
+ pkg = "pip-tools",
+ script = "pip-compile",
+ ),
+)
+```
+
+Note that for packages whose name and script are the same, only the name of the package
+is needed when calling the `entry_point` macro.
+
+```python
+load("@pip_deps//:requirements.bzl", "entry_point")
+
+alias(
+ name = "flake8",
+ actual = entry_point("flake8"),
+)
+```
+
+## Vendoring the requirements.bzl file
+
+In some cases you may not want to generate the requirements.bzl file as a repository rule
+while Bazel is fetching dependencies. For example, if you produce a reusable Bazel module
+such as a ruleset, you may want to include the requirements.bzl file rather than make your users
+install the WORKSPACE setup to generate it.
+See https://github.com/bazelbuild/rules_python/issues/608
+
+This is the same workflow as Gazelle, which creates `go_repository` rules with
+[`update-repos`](https://github.com/bazelbuild/bazel-gazelle#update-repos)
+
+To do this, use the "write to source file" pattern documented in
+https://blog.aspect.dev/bazel-can-write-to-the-source-folder
+to put a copy of the generated requirements.bzl into your project.
+Then load the requirements.bzl file directly rather than from the generated repository.
+See the example in rules_python/examples/pip_parse_vendored.
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="pip_parse-requirements"></a>requirements | Deprecated. See requirements_lock. | <code>None</code> |
+| <a id="pip_parse-requirements_lock"></a>requirements_lock | A fully resolved 'requirements.txt' pip requirement file containing the transitive set of your dependencies. If this file is passed instead of 'requirements' no resolve will take place and pip_repository will create individual repositories for each of your dependencies so that wheels are fetched/built only for the targets specified by 'build/run/test'. Note that if your lockfile is platform-dependent, you can use the <code>requirements_[platform]</code> attributes. | <code>None</code> |
+| <a id="pip_parse-name"></a>name | The name of the generated repository. The generated repositories containing each requirement will be of the form <code>&lt;name&gt;_&lt;requirement-name&gt;</code>. | <code>"pip_parsed_deps"</code> |
+| <a id="pip_parse-kwargs"></a>kwargs | Additional arguments to the [<code>pip_repository</code>](./pip_repository.md) repository rule. | none |
+
+
diff --git a/docs/pip_repository.md b/docs/pip_repository.md
new file mode 100644
index 0000000..8536052
--- /dev/null
+++ b/docs/pip_repository.md
@@ -0,0 +1,242 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+
+
+<a id="pip_hub_repository_bzlmod"></a>
+
+## pip_hub_repository_bzlmod
+
+<pre>
+pip_hub_repository_bzlmod(<a href="#pip_hub_repository_bzlmod-name">name</a>, <a href="#pip_hub_repository_bzlmod-repo_mapping">repo_mapping</a>, <a href="#pip_hub_repository_bzlmod-repo_name">repo_name</a>, <a href="#pip_hub_repository_bzlmod-whl_library_alias_names">whl_library_alias_names</a>)
+</pre>
+
+A rule for bzlmod mulitple pip repository creation. PRIVATE USE ONLY.
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="pip_hub_repository_bzlmod-name"></a>name | A unique name for this repository. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="pip_hub_repository_bzlmod-repo_mapping"></a>repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.&lt;p&gt;For example, an entry <code>"@foo": "@bar"</code> declares that, for any time this repository depends on <code>@foo</code> (such as a dependency on <code>@foo//some:target</code>, it should actually resolve that dependency within globally-declared <code>@bar</code> (<code>@bar//some:target</code>). | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | required | |
+| <a id="pip_hub_repository_bzlmod-repo_name"></a>repo_name | The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name. | String | required | |
+| <a id="pip_hub_repository_bzlmod-whl_library_alias_names"></a>whl_library_alias_names | The list of whl alias that we use to build aliases and the whl names | List of strings | required | |
+
+
+<a id="pip_repository"></a>
+
+## pip_repository
+
+<pre>
+pip_repository(<a href="#pip_repository-name">name</a>, <a href="#pip_repository-annotations">annotations</a>, <a href="#pip_repository-download_only">download_only</a>, <a href="#pip_repository-enable_implicit_namespace_pkgs">enable_implicit_namespace_pkgs</a>, <a href="#pip_repository-environment">environment</a>,
+ <a href="#pip_repository-extra_pip_args">extra_pip_args</a>, <a href="#pip_repository-incompatible_generate_aliases">incompatible_generate_aliases</a>, <a href="#pip_repository-isolated">isolated</a>, <a href="#pip_repository-pip_data_exclude">pip_data_exclude</a>,
+ <a href="#pip_repository-python_interpreter">python_interpreter</a>, <a href="#pip_repository-python_interpreter_target">python_interpreter_target</a>, <a href="#pip_repository-quiet">quiet</a>, <a href="#pip_repository-repo_mapping">repo_mapping</a>, <a href="#pip_repository-repo_prefix">repo_prefix</a>,
+ <a href="#pip_repository-requirements_darwin">requirements_darwin</a>, <a href="#pip_repository-requirements_linux">requirements_linux</a>, <a href="#pip_repository-requirements_lock">requirements_lock</a>, <a href="#pip_repository-requirements_windows">requirements_windows</a>,
+ <a href="#pip_repository-timeout">timeout</a>)
+</pre>
+
+A rule for importing `requirements.txt` dependencies into Bazel.
+
+This rule imports a `requirements.txt` file and generates a new
+`requirements.bzl` file. This is used via the `WORKSPACE` pattern:
+
+```python
+pip_repository(
+ name = "foo",
+ requirements = ":requirements.txt",
+)
+```
+
+You can then reference imported dependencies from your `BUILD` file with:
+
+```python
+load("@foo//:requirements.bzl", "requirement")
+py_library(
+ name = "bar",
+ ...
+ deps = [
+ "//my/other:dep",
+ requirement("requests"),
+ requirement("numpy"),
+ ],
+)
+```
+
+Or alternatively:
+```python
+load("@foo//:requirements.bzl", "all_requirements")
+py_binary(
+ name = "baz",
+ ...
+ deps = [
+ ":foo",
+ ] + all_requirements,
+)
+```
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="pip_repository-name"></a>name | A unique name for this repository. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="pip_repository-annotations"></a>annotations | Optional annotations to apply to packages | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
+| <a id="pip_repository-download_only"></a>download_only | Whether to use "pip download" instead of "pip wheel". Disables building wheels from source, but allows use of --platform, --python-version, --implementation, and --abi in --extra_pip_args to download wheels for a different platform from the host platform. | Boolean | optional | <code>False</code> |
+| <a id="pip_repository-enable_implicit_namespace_pkgs"></a>enable_implicit_namespace_pkgs | If true, disables conversion of native namespace packages into pkg-util style namespace packages. When set all py_binary and py_test targets must specify either <code>legacy_create_init=False</code> or the global Bazel option <code>--incompatible_default_to_explicit_init_py</code> to prevent <code>__init__.py</code> being automatically generated in every directory.<br><br>This option is required to support some packages which cannot handle the conversion to pkg-util style. | Boolean | optional | <code>False</code> |
+| <a id="pip_repository-environment"></a>environment | Environment variables to set in the pip subprocess. Can be used to set common variables such as <code>http_proxy</code>, <code>https_proxy</code> and <code>no_proxy</code> Note that pip is run with "--isolated" on the CLI so <code>PIP_&lt;VAR&gt;_&lt;NAME&gt;</code> style env vars are ignored, but env vars that control requests and urllib3 can be passed. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
+| <a id="pip_repository-extra_pip_args"></a>extra_pip_args | Extra arguments to pass on to pip. Must not contain spaces. | List of strings | optional | <code>[]</code> |
+| <a id="pip_repository-incompatible_generate_aliases"></a>incompatible_generate_aliases | Allow generating aliases '@pip//&lt;pkg&gt;' -&gt; '@pip_&lt;pkg&gt;//:pkg'. | Boolean | optional | <code>False</code> |
+| <a id="pip_repository-isolated"></a>isolated | Whether or not to pass the [--isolated](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-isolated) flag to the underlying pip command. Alternatively, the <code>RULES_PYTHON_PIP_ISOLATED</code> environment variable can be used to control this flag. | Boolean | optional | <code>True</code> |
+| <a id="pip_repository-pip_data_exclude"></a>pip_data_exclude | Additional data exclusion parameters to add to the pip packages BUILD file. | List of strings | optional | <code>[]</code> |
+| <a id="pip_repository-python_interpreter"></a>python_interpreter | The python interpreter to use. This can either be an absolute path or the name of a binary found on the host's <code>PATH</code> environment variable. If no value is set <code>python3</code> is defaulted for Unix systems and <code>python.exe</code> for Windows. | String | optional | <code>""</code> |
+| <a id="pip_repository-python_interpreter_target"></a>python_interpreter_target | If you are using a custom python interpreter built by another repository rule, use this attribute to specify its BUILD target. This allows pip_repository to invoke pip using the same interpreter as your toolchain. If set, takes precedence over python_interpreter. An example value: "@python3_x86_64-unknown-linux-gnu//:python". | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository-quiet"></a>quiet | If True, suppress printing stdout and stderr output to the terminal. | Boolean | optional | <code>True</code> |
+| <a id="pip_repository-repo_mapping"></a>repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.&lt;p&gt;For example, an entry <code>"@foo": "@bar"</code> declares that, for any time this repository depends on <code>@foo</code> (such as a dependency on <code>@foo//some:target</code>, it should actually resolve that dependency within globally-declared <code>@bar</code> (<code>@bar//some:target</code>). | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | required | |
+| <a id="pip_repository-repo_prefix"></a>repo_prefix | Prefix for the generated packages will be of the form <code>@&lt;prefix&gt;&lt;sanitized-package-name&gt;//...</code> | String | optional | <code>""</code> |
+| <a id="pip_repository-requirements_darwin"></a>requirements_darwin | Override the requirements_lock attribute when the host platform is Mac OS | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository-requirements_linux"></a>requirements_linux | Override the requirements_lock attribute when the host platform is Linux | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository-requirements_lock"></a>requirements_lock | A fully resolved 'requirements.txt' pip requirement file containing the transitive set of your dependencies. If this file is passed instead of 'requirements' no resolve will take place and pip_repository will create individual repositories for each of your dependencies so that wheels are fetched/built only for the targets specified by 'build/run/test'. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository-requirements_windows"></a>requirements_windows | Override the requirements_lock attribute when the host platform is Windows | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository-timeout"></a>timeout | Timeout (in seconds) on the rule's execution duration. | Integer | optional | <code>600</code> |
+
+
+<a id="pip_repository_bzlmod"></a>
+
+## pip_repository_bzlmod
+
+<pre>
+pip_repository_bzlmod(<a href="#pip_repository_bzlmod-name">name</a>, <a href="#pip_repository_bzlmod-repo_mapping">repo_mapping</a>, <a href="#pip_repository_bzlmod-repo_name">repo_name</a>, <a href="#pip_repository_bzlmod-requirements_darwin">requirements_darwin</a>, <a href="#pip_repository_bzlmod-requirements_linux">requirements_linux</a>,
+ <a href="#pip_repository_bzlmod-requirements_lock">requirements_lock</a>, <a href="#pip_repository_bzlmod-requirements_windows">requirements_windows</a>)
+</pre>
+
+A rule for bzlmod pip_repository creation. Intended for private use only.
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="pip_repository_bzlmod-name"></a>name | A unique name for this repository. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="pip_repository_bzlmod-repo_mapping"></a>repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.&lt;p&gt;For example, an entry <code>"@foo": "@bar"</code> declares that, for any time this repository depends on <code>@foo</code> (such as a dependency on <code>@foo//some:target</code>, it should actually resolve that dependency within globally-declared <code>@bar</code> (<code>@bar//some:target</code>). | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | required | |
+| <a id="pip_repository_bzlmod-repo_name"></a>repo_name | The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name | String | required | |
+| <a id="pip_repository_bzlmod-requirements_darwin"></a>requirements_darwin | Override the requirements_lock attribute when the host platform is Mac OS | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository_bzlmod-requirements_linux"></a>requirements_linux | Override the requirements_lock attribute when the host platform is Linux | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository_bzlmod-requirements_lock"></a>requirements_lock | A fully resolved 'requirements.txt' pip requirement file containing the transitive set of your dependencies. If this file is passed instead of 'requirements' no resolve will take place and pip_repository will create individual repositories for each of your dependencies so that wheels are fetched/built only for the targets specified by 'build/run/test'. | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="pip_repository_bzlmod-requirements_windows"></a>requirements_windows | Override the requirements_lock attribute when the host platform is Windows | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+
+
+<a id="whl_library"></a>
+
+## whl_library
+
+<pre>
+whl_library(<a href="#whl_library-name">name</a>, <a href="#whl_library-annotation">annotation</a>, <a href="#whl_library-download_only">download_only</a>, <a href="#whl_library-enable_implicit_namespace_pkgs">enable_implicit_namespace_pkgs</a>, <a href="#whl_library-environment">environment</a>,
+ <a href="#whl_library-extra_pip_args">extra_pip_args</a>, <a href="#whl_library-isolated">isolated</a>, <a href="#whl_library-pip_data_exclude">pip_data_exclude</a>, <a href="#whl_library-python_interpreter">python_interpreter</a>, <a href="#whl_library-python_interpreter_target">python_interpreter_target</a>,
+ <a href="#whl_library-quiet">quiet</a>, <a href="#whl_library-repo">repo</a>, <a href="#whl_library-repo_mapping">repo_mapping</a>, <a href="#whl_library-repo_prefix">repo_prefix</a>, <a href="#whl_library-requirement">requirement</a>, <a href="#whl_library-timeout">timeout</a>)
+</pre>
+
+
+Download and extracts a single wheel based into a bazel repo based on the requirement string passed in.
+Instantiated from pip_repository and inherits config options from there.
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="whl_library-name"></a>name | A unique name for this repository. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="whl_library-annotation"></a>annotation | Optional json encoded file containing annotation to apply to the extracted wheel. See <code>package_annotation</code> | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="whl_library-download_only"></a>download_only | Whether to use "pip download" instead of "pip wheel". Disables building wheels from source, but allows use of --platform, --python-version, --implementation, and --abi in --extra_pip_args to download wheels for a different platform from the host platform. | Boolean | optional | <code>False</code> |
+| <a id="whl_library-enable_implicit_namespace_pkgs"></a>enable_implicit_namespace_pkgs | If true, disables conversion of native namespace packages into pkg-util style namespace packages. When set all py_binary and py_test targets must specify either <code>legacy_create_init=False</code> or the global Bazel option <code>--incompatible_default_to_explicit_init_py</code> to prevent <code>__init__.py</code> being automatically generated in every directory.<br><br>This option is required to support some packages which cannot handle the conversion to pkg-util style. | Boolean | optional | <code>False</code> |
+| <a id="whl_library-environment"></a>environment | Environment variables to set in the pip subprocess. Can be used to set common variables such as <code>http_proxy</code>, <code>https_proxy</code> and <code>no_proxy</code> Note that pip is run with "--isolated" on the CLI so <code>PIP_&lt;VAR&gt;_&lt;NAME&gt;</code> style env vars are ignored, but env vars that control requests and urllib3 can be passed. | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | optional | <code>{}</code> |
+| <a id="whl_library-extra_pip_args"></a>extra_pip_args | Extra arguments to pass on to pip. Must not contain spaces. | List of strings | optional | <code>[]</code> |
+| <a id="whl_library-isolated"></a>isolated | Whether or not to pass the [--isolated](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-isolated) flag to the underlying pip command. Alternatively, the <code>RULES_PYTHON_PIP_ISOLATED</code> environment variable can be used to control this flag. | Boolean | optional | <code>True</code> |
+| <a id="whl_library-pip_data_exclude"></a>pip_data_exclude | Additional data exclusion parameters to add to the pip packages BUILD file. | List of strings | optional | <code>[]</code> |
+| <a id="whl_library-python_interpreter"></a>python_interpreter | The python interpreter to use. This can either be an absolute path or the name of a binary found on the host's <code>PATH</code> environment variable. If no value is set <code>python3</code> is defaulted for Unix systems and <code>python.exe</code> for Windows. | String | optional | <code>""</code> |
+| <a id="whl_library-python_interpreter_target"></a>python_interpreter_target | If you are using a custom python interpreter built by another repository rule, use this attribute to specify its BUILD target. This allows pip_repository to invoke pip using the same interpreter as your toolchain. If set, takes precedence over python_interpreter. An example value: "@python3_x86_64-unknown-linux-gnu//:python". | <a href="https://bazel.build/concepts/labels">Label</a> | optional | <code>None</code> |
+| <a id="whl_library-quiet"></a>quiet | If True, suppress printing stdout and stderr output to the terminal. | Boolean | optional | <code>True</code> |
+| <a id="whl_library-repo"></a>repo | Pointer to parent repo name. Used to make these rules rerun if the parent repo changes. | String | required | |
+| <a id="whl_library-repo_mapping"></a>repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.&lt;p&gt;For example, an entry <code>"@foo": "@bar"</code> declares that, for any time this repository depends on <code>@foo</code> (such as a dependency on <code>@foo//some:target</code>, it should actually resolve that dependency within globally-declared <code>@bar</code> (<code>@bar//some:target</code>). | <a href="https://bazel.build/rules/lib/dict">Dictionary: String -> String</a> | required | |
+| <a id="whl_library-repo_prefix"></a>repo_prefix | Prefix for the generated packages will be of the form <code>@&lt;prefix&gt;&lt;sanitized-package-name&gt;//...</code> | String | optional | <code>""</code> |
+| <a id="whl_library-requirement"></a>requirement | Python requirement string describing the package to make available | String | required | |
+| <a id="whl_library-timeout"></a>timeout | Timeout (in seconds) on the rule's execution duration. | Integer | optional | <code>600</code> |
+
+
+<a id="locked_requirements_label"></a>
+
+## locked_requirements_label
+
+<pre>
+locked_requirements_label(<a href="#locked_requirements_label-ctx">ctx</a>, <a href="#locked_requirements_label-attr">attr</a>)
+</pre>
+
+Get the preferred label for a locked requirements file based on platform.
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="locked_requirements_label-ctx"></a>ctx | repository or module context | none |
+| <a id="locked_requirements_label-attr"></a>attr | attributes for the repo rule or tag extension | none |
+
+**RETURNS**
+
+Label
+
+
+<a id="package_annotation"></a>
+
+## package_annotation
+
+<pre>
+package_annotation(<a href="#package_annotation-additive_build_content">additive_build_content</a>, <a href="#package_annotation-copy_files">copy_files</a>, <a href="#package_annotation-copy_executables">copy_executables</a>, <a href="#package_annotation-data">data</a>, <a href="#package_annotation-data_exclude_glob">data_exclude_glob</a>,
+ <a href="#package_annotation-srcs_exclude_glob">srcs_exclude_glob</a>)
+</pre>
+
+Annotations to apply to the BUILD file content from package generated from a `pip_repository` rule.
+
+[cf]: https://github.com/bazelbuild/bazel-skylib/blob/main/docs/copy_file_doc.md
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="package_annotation-additive_build_content"></a>additive_build_content | Raw text to add to the generated <code>BUILD</code> file of a package. | <code>None</code> |
+| <a id="package_annotation-copy_files"></a>copy_files | A mapping of <code>src</code> and <code>out</code> files for [@bazel_skylib//rules:copy_file.bzl][cf] | <code>{}</code> |
+| <a id="package_annotation-copy_executables"></a>copy_executables | A mapping of <code>src</code> and <code>out</code> files for [@bazel_skylib//rules:copy_file.bzl][cf]. Targets generated here will also be flagged as executable. | <code>{}</code> |
+| <a id="package_annotation-data"></a>data | A list of labels to add as <code>data</code> dependencies to the generated <code>py_library</code> target. | <code>[]</code> |
+| <a id="package_annotation-data_exclude_glob"></a>data_exclude_glob | A list of exclude glob patterns to add as <code>data</code> to the generated <code>py_library</code> target. | <code>[]</code> |
+| <a id="package_annotation-srcs_exclude_glob"></a>srcs_exclude_glob | A list of labels to add as <code>srcs</code> to the generated <code>py_library</code> target. | <code>[]</code> |
+
+**RETURNS**
+
+str: A json encoded string of the provided content.
+
+
+<a id="use_isolated"></a>
+
+## use_isolated
+
+<pre>
+use_isolated(<a href="#use_isolated-ctx">ctx</a>, <a href="#use_isolated-attr">attr</a>)
+</pre>
+
+Determine whether or not to pass the pip `--isolated` flag to the pip invocation.
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="use_isolated-ctx"></a>ctx | repository or module context | none |
+| <a id="use_isolated-attr"></a>attr | attributes for the repo rule or tag extension | none |
+
+**RETURNS**
+
+True if --isolated should be passed
+
+
diff --git a/docs/py_cc_toolchain.md b/docs/py_cc_toolchain.md
new file mode 100644
index 0000000..3a59ea9
--- /dev/null
+++ b/docs/py_cc_toolchain.md
@@ -0,0 +1,32 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Implementation of py_cc_toolchain rule.
+
+NOTE: This is a beta-quality feature. APIs subject to change until
+https://github.com/bazelbuild/rules_python/issues/824 is considered done.
+
+
+<a id="py_cc_toolchain"></a>
+
+## py_cc_toolchain
+
+<pre>
+py_cc_toolchain(<a href="#py_cc_toolchain-name">name</a>, <a href="#py_cc_toolchain-headers">headers</a>, <a href="#py_cc_toolchain-python_version">python_version</a>)
+</pre>
+
+A toolchain for a Python runtime's C/C++ information (e.g. headers)
+
+This rule carries information about the C/C++ side of a Python runtime, e.g.
+headers, shared libraries, etc.
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="py_cc_toolchain-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="py_cc_toolchain-headers"></a>headers | Target that provides the Python headers. Typically this is a cc_library target. | <a href="https://bazel.build/concepts/labels">Label</a> | required | |
+| <a id="py_cc_toolchain-python_version"></a>python_version | The Major.minor Python version, e.g. 3.11 | String | required | |
+
+
diff --git a/docs/py_cc_toolchain_info.md b/docs/py_cc_toolchain_info.md
new file mode 100644
index 0000000..4e59a78
--- /dev/null
+++ b/docs/py_cc_toolchain_info.md
@@ -0,0 +1,27 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Provider for C/C++ information about the Python runtime.
+
+NOTE: This is a beta-quality feature. APIs subject to change until
+https://github.com/bazelbuild/rules_python/issues/824 is considered done.
+
+
+<a id="PyCcToolchainInfo"></a>
+
+## PyCcToolchainInfo
+
+<pre>
+PyCcToolchainInfo(<a href="#PyCcToolchainInfo-headers">headers</a>, <a href="#PyCcToolchainInfo-python_version">python_version</a>)
+</pre>
+
+C/C++ information about the Python runtime.
+
+**FIELDS**
+
+
+| Name | Description |
+| :------------- | :------------- |
+| <a id="PyCcToolchainInfo-headers"></a>headers | (struct) Information about the header files, with fields: * providers_map: a dict of string to provider instances. The key should be a fully qualified name (e.g. <code>@rules_foo//bar:baz.bzl#MyInfo</code>) of the provider to uniquely identify its type.<br><br> The following keys are always present: * CcInfo: the CcInfo provider instance for the headers. * DefaultInfo: the DefaultInfo provider instance for the headers.<br><br> A map is used to allow additional providers from the originating headers target (typically a <code>cc_library</code>) to be propagated to consumers (directly exposing a Target object can cause memory issues and is an anti-pattern).<br><br> When consuming this map, it's suggested to use <code>providers_map.values()</code> to return all providers; or copy the map and filter out or replace keys as appropriate. Note that any keys begining with <code>_</code> (underscore) are considered private and should be forward along as-is (this better allows e.g. <code>:current_py_cc_headers</code> to act as the underlying headers target it represents). |
+| <a id="PyCcToolchainInfo-python_version"></a>python_version | (str) The Python Major.Minor version. |
+
+
diff --git a/docs/python.md b/docs/python.md
new file mode 100755
index 0000000..e42375a
--- /dev/null
+++ b/docs/python.md
@@ -0,0 +1,227 @@
+<!-- Generated with Stardoc: http://skydoc.bazel.build -->
+
+Core rules for building Python projects.
+
+<a id="current_py_toolchain"></a>
+
+## current_py_toolchain
+
+<pre>
+current_py_toolchain(<a href="#current_py_toolchain-name">name</a>)
+</pre>
+
+
+ This rule exists so that the current python toolchain can be used in the `toolchains` attribute of
+ other rules, such as genrule. It allows exposing a python toolchain after toolchain resolution has
+ happened, to a rule which expects a concrete implementation of a toolchain, rather than a
+ toolchain_type which could be resolved to that toolchain.
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="current_py_toolchain-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+
+
+<a id="py_import"></a>
+
+## py_import
+
+<pre>
+py_import(<a href="#py_import-name">name</a>, <a href="#py_import-deps">deps</a>, <a href="#py_import-srcs">srcs</a>)
+</pre>
+
+This rule allows the use of Python packages as dependencies.
+
+ It imports the given `.egg` file(s), which might be checked in source files,
+ fetched externally as with `http_file`, or produced as outputs of other rules.
+
+ It may be used like a `py_library`, in the `deps` of other Python rules.
+
+ This is similar to [java_import](https://docs.bazel.build/versions/master/be/java.html#java_import).
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="py_import-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+| <a id="py_import-deps"></a>deps | The list of other libraries to be linked in to the binary target. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
+| <a id="py_import-srcs"></a>srcs | The list of Python package files provided to Python targets that depend on this target. Note that currently only the .egg format is accepted. For .whl files, try the whl_library rule. We accept contributions to extend py_import to handle .whl. | <a href="https://bazel.build/concepts/labels">List of labels</a> | optional | <code>[]</code> |
+
+
+<a id="py_binary"></a>
+
+## py_binary
+
+<pre>
+py_binary(<a href="#py_binary-attrs">attrs</a>)
+</pre>
+
+See the Bazel core [py_binary](https://docs.bazel.build/versions/master/be/python.html#py_binary) documentation.
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="py_binary-attrs"></a>attrs | Rule attributes | none |
+
+
+<a id="py_library"></a>
+
+## py_library
+
+<pre>
+py_library(<a href="#py_library-attrs">attrs</a>)
+</pre>
+
+See the Bazel core [py_library](https://docs.bazel.build/versions/master/be/python.html#py_library) documentation.
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="py_library-attrs"></a>attrs | Rule attributes | none |
+
+
+<a id="py_runtime"></a>
+
+## py_runtime
+
+<pre>
+py_runtime(<a href="#py_runtime-attrs">attrs</a>)
+</pre>
+
+See the Bazel core [py_runtime](https://docs.bazel.build/versions/master/be/python.html#py_runtime) documentation.
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="py_runtime-attrs"></a>attrs | Rule attributes | none |
+
+
+<a id="py_runtime_pair"></a>
+
+## py_runtime_pair
+
+<pre>
+py_runtime_pair(<a href="#py_runtime_pair-name">name</a>, <a href="#py_runtime_pair-py2_runtime">py2_runtime</a>, <a href="#py_runtime_pair-py3_runtime">py3_runtime</a>, <a href="#py_runtime_pair-attrs">attrs</a>)
+</pre>
+
+A toolchain rule for Python.
+
+This used to wrap up to two Python runtimes, one for Python 2 and one for Python 3.
+However, Python 2 is no longer supported, so it now only wraps a single Python 3
+runtime.
+
+Usually the wrapped runtimes are declared using the `py_runtime` rule, but any
+rule returning a `PyRuntimeInfo` provider may be used.
+
+This rule returns a `platform_common.ToolchainInfo` provider with the following
+schema:
+
+```python
+platform_common.ToolchainInfo(
+ py2_runtime = None,
+ py3_runtime = &lt;PyRuntimeInfo or None&gt;,
+)
+```
+
+Example usage:
+
+```python
+# In your BUILD file...
+
+load("@rules_python//python:defs.bzl", "py_runtime_pair")
+
+py_runtime(
+ name = "my_py3_runtime",
+ interpreter_path = "/system/python3",
+ python_version = "PY3",
+)
+
+py_runtime_pair(
+ name = "my_py_runtime_pair",
+ py3_runtime = ":my_py3_runtime",
+)
+
+toolchain(
+ name = "my_toolchain",
+ target_compatible_with = &lt;...&gt;,
+ toolchain = ":my_py_runtime_pair",
+ toolchain_type = "@rules_python//python:toolchain_type",
+)
+```
+
+```python
+# In your WORKSPACE...
+
+register_toolchains("//my_pkg:my_toolchain")
+```
+
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="py_runtime_pair-name"></a>name | str, the name of the target | none |
+| <a id="py_runtime_pair-py2_runtime"></a>py2_runtime | optional Label; must be unset or None; an error is raised otherwise. | <code>None</code> |
+| <a id="py_runtime_pair-py3_runtime"></a>py3_runtime | Label; a target with <code>PyRuntimeInfo</code> for Python 3. | <code>None</code> |
+| <a id="py_runtime_pair-attrs"></a>attrs | Extra attrs passed onto the native rule | none |
+
+
+<a id="py_test"></a>
+
+## py_test
+
+<pre>
+py_test(<a href="#py_test-attrs">attrs</a>)
+</pre>
+
+See the Bazel core [py_test](https://docs.bazel.build/versions/master/be/python.html#py_test) documentation.
+
+**PARAMETERS**
+
+
+| Name | Description | Default Value |
+| :------------- | :------------- | :------------- |
+| <a id="py_test-attrs"></a>attrs | Rule attributes | none |
+
+
+<a id="find_requirements"></a>
+
+## find_requirements
+
+<pre>
+find_requirements(<a href="#find_requirements-name">name</a>)
+</pre>
+
+The aspect definition. Can be invoked on the command line as
+
+ bazel build //pkg:my_py_binary_target --aspects=@rules_python//python:defs.bzl%find_requirements --output_groups=pyversioninfo
+
+
+**ASPECT ATTRIBUTES**
+
+
+| Name | Type |
+| :------------- | :------------- |
+| deps| String |
+
+
+**ATTRIBUTES**
+
+
+| Name | Description | Type | Mandatory | Default |
+| :------------- | :------------- | :------------- | :------------- | :------------- |
+| <a id="find_requirements-name"></a>name | A unique name for this target. | <a href="https://bazel.build/concepts/labels#target-names">Name</a> | required | |
+
+
diff --git a/examples/BUILD.bazel b/examples/BUILD.bazel
new file mode 100644
index 0000000..feb1cfb
--- /dev/null
+++ b/examples/BUILD.bazel
@@ -0,0 +1,72 @@
+# Copyright 2017 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("//tools/bazel_integration_test:bazel_integration_test.bzl", "bazel_integration_test")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"]) # Apache 2.0
+
+bazel_integration_test(
+ name = "build_file_generation_example",
+ timeout = "long",
+)
+
+bazel_integration_test(
+ name = "bzlmod_build_file_generation_example",
+ timeout = "long",
+)
+
+bazel_integration_test(
+ name = "pip_install_example",
+ timeout = "long",
+)
+
+bazel_integration_test(
+ name = "pip_parse_example",
+ timeout = "long",
+)
+
+bazel_integration_test(
+ name = "pip_parse_vendored_example",
+ timeout = "long",
+ tags = ["fix-windows"],
+)
+
+bazel_integration_test(
+ name = "pip_repository_annotations_example",
+ timeout = "long",
+)
+
+bazel_integration_test(
+ name = "py_proto_library_example",
+ timeout = "long",
+)
+
+bazel_integration_test(
+ name = "py_proto_library_example_bzlmod",
+ timeout = "long",
+ bzlmod = True,
+ dirname = "py_proto_library",
+)
+
+bazel_integration_test(
+ name = "multi_python_versions_example",
+ timeout = "long",
+)
+
+bazel_integration_test(
+ name = "bzlmod_example",
+ bzlmod = True,
+ override_bazel_version = "6.0.0",
+)
diff --git a/examples/build_file_generation/.bazelrc b/examples/build_file_generation/.bazelrc
new file mode 100644
index 0000000..28f634b
--- /dev/null
+++ b/examples/build_file_generation/.bazelrc
@@ -0,0 +1,5 @@
+test --test_output=errors --enable_runfiles
+
+# Windows requires these for multi-python support:
+build --enable_runfiles
+startup --windows_enable_symlinks
diff --git a/examples/build_file_generation/.gitignore b/examples/build_file_generation/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/examples/build_file_generation/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/examples/build_file_generation/BUILD.bazel b/examples/build_file_generation/BUILD.bazel
new file mode 100644
index 0000000..928fb12
--- /dev/null
+++ b/examples/build_file_generation/BUILD.bazel
@@ -0,0 +1,106 @@
+# Load various rules so that we can have bazel download
+# various rulesets and dependencies.
+# The `load` statement imports the symbol for the rule, in the defined
+# ruleset. When the symbol is loaded you can use the rule.
+load("@bazel_gazelle//:def.bzl", "gazelle")
+load("@pip//:requirements.bzl", "all_whl_requirements")
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+load("@rules_python_gazelle_plugin//:def.bzl", "GAZELLE_PYTHON_RUNTIME_DEPS")
+load("@rules_python_gazelle_plugin//manifest:defs.bzl", "gazelle_python_manifest")
+load("@rules_python_gazelle_plugin//modules_mapping:def.bzl", "modules_mapping")
+
+compile_pip_requirements(
+ name = "requirements",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock.txt",
+ requirements_windows = "requirements_windows.txt",
+)
+
+# This repository rule fetches the metadata for python packages we
+# depend on. That data is required for the gazelle_python_manifest
+# rule to update our manifest file.
+# To see what this rule does, try `bazel run @modules_map//:print`
+modules_mapping(
+ name = "modules_map",
+ exclude_patterns = [
+ "^_|(\\._)+", # This is the default.
+ "(\\.tests)+", # Add a custom one to get rid of the psutil tests.
+ ],
+ wheels = all_whl_requirements,
+)
+
+# Gazelle python extension needs a manifest file mapping from
+# an import to the installed package that provides it.
+# This macro produces two targets:
+# - //:gazelle_python_manifest.update can be used with `bazel run`
+# to recalculate the manifest
+# - //:gazelle_python_manifest.test is a test target ensuring that
+# the manifest doesn't need to be updated
+gazelle_python_manifest(
+ name = "gazelle_python_manifest",
+ modules_mapping = ":modules_map",
+ pip_repository_name = "pip",
+ # NOTE: We can pass a list just like in `bzlmod_build_file_generation` example
+ # but we keep a single target here for regression testing.
+ requirements = "//:requirements_lock.txt",
+ # NOTE: we can use this flag in order to make our setup compatible with
+ # bzlmod.
+ use_pip_repository_aliases = True,
+)
+
+# Our gazelle target points to the python gazelle binary.
+# This is the simple case where we only need one language supported.
+# If you also had proto, go, or other gazelle-supported languages,
+# you would also need a gazelle_binary rule.
+# See https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.rst#example
+gazelle(
+ name = "gazelle",
+ data = GAZELLE_PYTHON_RUNTIME_DEPS,
+ gazelle = "@rules_python_gazelle_plugin//python:gazelle_binary",
+)
+
+# This rule is auto-generated and managed by Gazelle,
+# because it found the __init__.py file in this folder.
+# See: https://bazel.build/reference/be/python#py_library
+py_library(
+ name = "build_file_generation",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//random_number_generator",
+ "@pip//flask",
+ ],
+)
+
+# A py_binary is an executable Python program consisting of a collection of .py source files.
+# See: https://bazel.build/reference/be/python#py_binary
+#
+# This rule is auto-generated and managed by Gazelle,
+# because it found the __main__.py file in this folder.
+# This rule creates a target named //:build_file_generation_bin and you can use
+# bazel to run the target:
+# `bazel run //:build_file_generation_bin`
+py_binary(
+ name = "build_file_generation_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [":build_file_generation"],
+)
+
+# A py_test is a Python unit test.
+# See: https://bazel.build/reference/be/python#py_test
+#
+# This rule is auto-generated and managed by Gazelle,
+# because it found the __test__.py file in this folder.
+# This rule creates a target named //:build_file_generation_test and you can use
+# bazel to run the target:
+# `bazel test //:build_file_generation_test`
+py_test(
+ name = "build_file_generation_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":build_file_generation"],
+)
diff --git a/examples/build_file_generation/README.md b/examples/build_file_generation/README.md
new file mode 100644
index 0000000..cd3cd1f
--- /dev/null
+++ b/examples/build_file_generation/README.md
@@ -0,0 +1,22 @@
+# Build file generation with Gazelle
+
+This example shows a project that has Gazelle setup with the rules_python
+extension, so that targets like `py_library` and `py_binary` can be
+automatically created just by running
+
+```sh
+bazel run //:requirements.update
+bazel run //:gazelle_python_manifest.update
+bazel run //:gazelle
+```
+
+As a demo, try creating a `__main__.py` file in this directory, then
+re-run that gazelle command. You'll see that a `py_binary` target
+is created in the `BUILD` file.
+
+Or, try importing the `requests` library in `__init__.py`.
+You'll see that `deps = ["@pip//pypi__requests"]` is automatically
+added to the `py_library` target in the `BUILD` file.
+
+For more information on the behavior of the rules_python gazelle extension,
+see the README.md file in the /gazelle folder.
diff --git a/examples/build_file_generation/WORKSPACE b/examples/build_file_generation/WORKSPACE
new file mode 100644
index 0000000..7c74835
--- /dev/null
+++ b/examples/build_file_generation/WORKSPACE
@@ -0,0 +1,123 @@
+# Set the name of the bazel workspace.
+workspace(name = "build_file_generation_example")
+
+# Load the http_archive rule so that we can have bazel download
+# various rulesets and dependencies.
+# The `load` statement imports the symbol for http_archive from the http.bzl
+# file. When the symbol is loaded you can use the rule.
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+######################################################################
+# We need rules_go and bazel_gazelle, to build the gazelle plugin from source.
+# Setup instructions for this section are at
+# https://github.com/bazelbuild/bazel-gazelle#running-gazelle-with-bazel
+# You may need to update the version of the rule, which is listed in the above
+# documentation.
+######################################################################
+
+# Define an http_archive rule that will download the below ruleset,
+# test the sha, and extract the ruleset to you local bazel cache.
+
+http_archive(
+ name = "io_bazel_rules_go",
+ sha256 = "6dc2da7ab4cf5d7bfc7c949776b1b7c733f05e56edc4bcd9022bb249d2e2a996",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.39.1/rules_go-v0.39.1.zip",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.39.1/rules_go-v0.39.1.zip",
+ ],
+)
+
+# Download the bazel_gazelle ruleset.
+
+http_archive(
+ name = "bazel_gazelle",
+ sha256 = "727f3e4edd96ea20c29e8c2ca9e8d2af724d8c7778e7923a854b2c80952bc405",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.30.0/bazel-gazelle-v0.30.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.30.0/bazel-gazelle-v0.30.0.tar.gz",
+ ],
+)
+
+# Load rules_go ruleset and expose the toolchain and dep rules.
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
+load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
+
+# go_rules_dependencies is a function that registers external dependencies
+# needed by the Go rules.
+# See: https://github.com/bazelbuild/rules_go/blob/master/go/dependencies.rst#go_rules_dependencies
+go_rules_dependencies()
+
+# go_rules_dependencies is a function that registers external dependencies
+# needed by the Go rules.
+# See: https://github.com/bazelbuild/rules_go/blob/master/go/dependencies.rst#go_rules_dependencies
+go_register_toolchains(version = "1.19.4")
+
+# The following call configured the gazelle dependencies, Go environment and Go SDK.
+gazelle_dependencies()
+
+# Remaining setup is for rules_python.
+
+# DON'T COPY_PASTE THIS.
+# Our example uses `local_repository` to point to the HEAD version of rules_python.
+# Users should instead use the installation instructions from the release they use.
+# See https://github.com/bazelbuild/rules_python/releases
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+local_repository(
+ name = "rules_python_gazelle_plugin",
+ path = "../../gazelle",
+)
+
+# Next we load the toolchain from rules_python.
+load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+
+# We now register a hermetic Python interpreter rather than relying on a system-installed interpreter.
+# This toolchain will allow bazel to download a specific python version, and use that version
+# for compilation.
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+# Load the interpreter and pip_parse rules.
+load("@python39//:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+# This macro wraps the `pip_repository` rule that invokes `pip`, with `incremental` set.
+# Accepts a locked/compiled requirements file and installs the dependencies listed within.
+# Those dependencies become available in a generated `requirements.bzl` file.
+# You can instead check this `requirements.bzl` file into your repo.
+pip_parse(
+ name = "pip",
+ # Generate user friendly alias labels for each dependency that we have.
+ incompatible_generate_aliases = True,
+ # (Optional) You can provide a python_interpreter (path) or a python_interpreter_target (a Bazel target, that
+ # acts as an executable). The latter can be anything that could be used as Python interpreter. E.g.:
+ # 1. Python interpreter that you compile in the build file.
+ # 2. Pre-compiled python interpreter included with http_archive.
+ # 3. Wrapper script, like in the autodetecting python toolchain.
+ #
+ # Here, we use the interpreter constant that resolves to the host interpreter from the default Python toolchain.
+ python_interpreter_target = interpreter,
+ # Set the location of the lock file.
+ requirements_lock = "//:requirements_lock.txt",
+ requirements_windows = "//:requirements_windows.txt",
+)
+
+# Load the install_deps macro.
+load("@pip//:requirements.bzl", "install_deps")
+
+# Initialize repositories for all packages in requirements_lock.txt.
+install_deps()
+
+# The rules_python gazelle extension has some third-party go dependencies
+# which we need to fetch in order to compile it.
+load("@rules_python_gazelle_plugin//:deps.bzl", _py_gazelle_deps = "gazelle_deps")
+
+# See: https://github.com/bazelbuild/rules_python/blob/main/gazelle/README.md
+# This rule loads and compiles various go dependencies that running gazelle
+# for python requirements.
+_py_gazelle_deps()
diff --git a/examples/build_file_generation/__init__.py b/examples/build_file_generation/__init__.py
new file mode 100644
index 0000000..add73da
--- /dev/null
+++ b/examples/build_file_generation/__init__.py
@@ -0,0 +1,26 @@
+# Copyright 2022 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.
+
+from flask import Flask, jsonify
+from random_number_generator import generate_random_number
+
+app = Flask(__name__)
+
+@app.route('/random-number', methods=['GET'])
+def get_random_number():
+ return jsonify({'number': generate_random_number.generate_random_number()})
+
+"""Start the python web server"""
+def main():
+ app.run()
diff --git a/examples/build_file_generation/__main__.py b/examples/build_file_generation/__main__.py
new file mode 100644
index 0000000..8f8efba
--- /dev/null
+++ b/examples/build_file_generation/__main__.py
@@ -0,0 +1,18 @@
+# Copyright 2022 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.
+
+from __init__ import main
+
+if __name__ == '__main__':
+ main()
diff --git a/examples/build_file_generation/__test__.py b/examples/build_file_generation/__test__.py
new file mode 100644
index 0000000..c4fa5ef
--- /dev/null
+++ b/examples/build_file_generation/__test__.py
@@ -0,0 +1,28 @@
+# Copyright 2022 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 unittest
+from __init__ import app
+
+class TestServer(unittest.TestCase):
+ def setUp(self):
+ self.app = app.test_client()
+
+ def test_get_random_number(self):
+ response = self.app.get('/random-number')
+ self.assertEqual(response.status_code, 200)
+ self.assertIn('number', response.json)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/examples/build_file_generation/gazelle_python.yaml b/examples/build_file_generation/gazelle_python.yaml
new file mode 100644
index 0000000..1000757
--- /dev/null
+++ b/examples/build_file_generation/gazelle_python.yaml
@@ -0,0 +1,118 @@
+# GENERATED FILE - DO NOT EDIT!
+#
+# To update this file, run:
+# bazel run //:gazelle_python_manifest.update
+
+manifest:
+ modules_mapping:
+ click: click
+ click.core: click
+ click.decorators: click
+ click.exceptions: click
+ click.formatting: click
+ click.globals: click
+ click.parser: click
+ click.shell_completion: click
+ click.termui: click
+ click.testing: click
+ click.types: click
+ click.utils: click
+ flask: Flask
+ flask.app: Flask
+ flask.blueprints: Flask
+ flask.cli: Flask
+ flask.config: Flask
+ flask.ctx: Flask
+ flask.debughelpers: Flask
+ flask.globals: Flask
+ flask.helpers: Flask
+ flask.json: Flask
+ flask.json.provider: Flask
+ flask.json.tag: Flask
+ flask.logging: Flask
+ flask.scaffold: Flask
+ flask.sessions: Flask
+ flask.signals: Flask
+ flask.templating: Flask
+ flask.testing: Flask
+ flask.typing: Flask
+ flask.views: Flask
+ flask.wrappers: Flask
+ importlib_metadata: importlib_metadata
+ itsdangerous: itsdangerous
+ itsdangerous.encoding: itsdangerous
+ itsdangerous.exc: itsdangerous
+ itsdangerous.serializer: itsdangerous
+ itsdangerous.signer: itsdangerous
+ itsdangerous.timed: itsdangerous
+ itsdangerous.url_safe: itsdangerous
+ jinja2: Jinja2
+ jinja2.async_utils: Jinja2
+ jinja2.bccache: Jinja2
+ jinja2.compiler: Jinja2
+ jinja2.constants: Jinja2
+ jinja2.debug: Jinja2
+ jinja2.defaults: Jinja2
+ jinja2.environment: Jinja2
+ jinja2.exceptions: Jinja2
+ jinja2.ext: Jinja2
+ jinja2.filters: Jinja2
+ jinja2.idtracking: Jinja2
+ jinja2.lexer: Jinja2
+ jinja2.loaders: Jinja2
+ jinja2.meta: Jinja2
+ jinja2.nativetypes: Jinja2
+ jinja2.nodes: Jinja2
+ jinja2.optimizer: Jinja2
+ jinja2.parser: Jinja2
+ jinja2.runtime: Jinja2
+ jinja2.sandbox: Jinja2
+ jinja2.utils: Jinja2
+ jinja2.visitor: Jinja2
+ markupsafe: MarkupSafe
+ werkzeug: Werkzeug
+ werkzeug.datastructures: Werkzeug
+ werkzeug.debug: Werkzeug
+ werkzeug.debug.console: Werkzeug
+ werkzeug.debug.repr: Werkzeug
+ werkzeug.debug.tbtools: Werkzeug
+ werkzeug.exceptions: Werkzeug
+ werkzeug.formparser: Werkzeug
+ werkzeug.http: Werkzeug
+ werkzeug.local: Werkzeug
+ werkzeug.middleware: Werkzeug
+ werkzeug.middleware.dispatcher: Werkzeug
+ werkzeug.middleware.http_proxy: Werkzeug
+ werkzeug.middleware.lint: Werkzeug
+ werkzeug.middleware.profiler: Werkzeug
+ werkzeug.middleware.proxy_fix: Werkzeug
+ werkzeug.middleware.shared_data: Werkzeug
+ werkzeug.routing: Werkzeug
+ werkzeug.routing.converters: Werkzeug
+ werkzeug.routing.exceptions: Werkzeug
+ werkzeug.routing.map: Werkzeug
+ werkzeug.routing.matcher: Werkzeug
+ werkzeug.routing.rules: Werkzeug
+ werkzeug.sansio: Werkzeug
+ werkzeug.sansio.http: Werkzeug
+ werkzeug.sansio.multipart: Werkzeug
+ werkzeug.sansio.request: Werkzeug
+ werkzeug.sansio.response: Werkzeug
+ werkzeug.sansio.utils: Werkzeug
+ werkzeug.security: Werkzeug
+ werkzeug.serving: Werkzeug
+ werkzeug.test: Werkzeug
+ werkzeug.testapp: Werkzeug
+ werkzeug.urls: Werkzeug
+ werkzeug.user_agent: Werkzeug
+ werkzeug.utils: Werkzeug
+ werkzeug.wrappers: Werkzeug
+ werkzeug.wrappers.request: Werkzeug
+ werkzeug.wrappers.response: Werkzeug
+ werkzeug.wsgi: Werkzeug
+ zipp: zipp
+ zipp.py310compat: zipp
+ pip_repository:
+ name: pip
+ use_pip_repository_aliases: true
+integrity: 030d6d99b56c32d6577e616b617260d0a93588af791269162e43391a5a4fa576
diff --git a/examples/build_file_generation/random_number_generator/BUILD.bazel b/examples/build_file_generation/random_number_generator/BUILD.bazel
new file mode 100644
index 0000000..95e16fd
--- /dev/null
+++ b/examples/build_file_generation/random_number_generator/BUILD.bazel
@@ -0,0 +1,19 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "random_number_generator",
+ srcs = [
+ "__init__.py",
+ "generate_random_number.py",
+ ],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "random_number_generator_test",
+ srcs = ["__test__.py"],
+ imports = [".."],
+ main = "__test__.py",
+ deps = [":random_number_generator"],
+)
diff --git a/examples/build_file_generation/random_number_generator/__init__.py b/examples/build_file_generation/random_number_generator/__init__.py
new file mode 100644
index 0000000..bbdfb4c
--- /dev/null
+++ b/examples/build_file_generation/random_number_generator/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/examples/build_file_generation/random_number_generator/__test__.py b/examples/build_file_generation/random_number_generator/__test__.py
new file mode 100644
index 0000000..8cfb235
--- /dev/null
+++ b/examples/build_file_generation/random_number_generator/__test__.py
@@ -0,0 +1,25 @@
+# Copyright 2022 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 unittest
+import random_number_generator.generate_random_number as generate_random_number
+
+class TestRandomNumberGenerator(unittest.TestCase):
+ def test_generate_random_number(self):
+ number = generate_random_number.generate_random_number()
+ self.assertGreaterEqual(number, 1)
+ self.assertLessEqual(number, 10)
+
+if __name__ == '__main__':
+ unittest.main()
diff --git a/examples/build_file_generation/random_number_generator/generate_random_number.py b/examples/build_file_generation/random_number_generator/generate_random_number.py
new file mode 100644
index 0000000..e198b5b
--- /dev/null
+++ b/examples/build_file_generation/random_number_generator/generate_random_number.py
@@ -0,0 +1,19 @@
+# Copyright 2022 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 random
+
+"""Generate a random number"""
+def generate_random_number():
+ return random.randint(1, 10)
diff --git a/examples/build_file_generation/requirements.in b/examples/build_file_generation/requirements.in
new file mode 100644
index 0000000..7e10602
--- /dev/null
+++ b/examples/build_file_generation/requirements.in
@@ -0,0 +1 @@
+flask
diff --git a/examples/build_file_generation/requirements_lock.txt b/examples/build_file_generation/requirements_lock.txt
new file mode 100644
index 0000000..443db71
--- /dev/null
+++ b/examples/build_file_generation/requirements_lock.txt
@@ -0,0 +1,78 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+click==8.1.3 \
+ --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
+ --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
+ # via flask
+flask==2.2.2 \
+ --hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \
+ --hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526
+ # via -r requirements.in
+importlib-metadata==5.2.0 \
+ --hash=sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f \
+ --hash=sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd
+ # via flask
+itsdangerous==2.1.2 \
+ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \
+ --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a
+ # via flask
+jinja2==3.1.2 \
+ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
+ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
+ # via flask
+markupsafe==2.1.1 \
+ --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \
+ --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \
+ --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \
+ --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \
+ --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \
+ --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \
+ --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \
+ --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \
+ --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \
+ --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \
+ --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \
+ --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \
+ --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \
+ --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \
+ --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \
+ --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \
+ --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \
+ --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \
+ --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \
+ --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \
+ --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \
+ --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \
+ --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \
+ --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \
+ --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \
+ --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \
+ --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \
+ --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \
+ --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \
+ --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \
+ --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \
+ --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \
+ --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \
+ --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \
+ --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \
+ --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \
+ --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \
+ --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \
+ --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \
+ --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7
+ # via
+ # jinja2
+ # werkzeug
+werkzeug==2.2.2 \
+ --hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \
+ --hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5
+ # via flask
+zipp==3.11.0 \
+ --hash=sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa \
+ --hash=sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766
+ # via importlib-metadata
diff --git a/examples/build_file_generation/requirements_windows.txt b/examples/build_file_generation/requirements_windows.txt
new file mode 100644
index 0000000..bdd536f
--- /dev/null
+++ b/examples/build_file_generation/requirements_windows.txt
@@ -0,0 +1,82 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+click==8.1.3 \
+ --hash=sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e \
+ --hash=sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48
+ # via flask
+colorama==0.4.6 \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+ # via click
+flask==2.2.2 \
+ --hash=sha256:642c450d19c4ad482f96729bd2a8f6d32554aa1e231f4f6b4e7e5264b16cca2b \
+ --hash=sha256:b9c46cc36662a7949f34b52d8ec7bb59c0d74ba08ba6cb9ce9adc1d8676d9526
+ # via -r requirements.in
+importlib-metadata==5.2.0 \
+ --hash=sha256:0eafa39ba42bf225fc00e67f701d71f85aead9f878569caf13c3724f704b970f \
+ --hash=sha256:404d48d62bba0b7a77ff9d405efd91501bef2e67ff4ace0bed40a0cf28c3c7cd
+ # via flask
+itsdangerous==2.1.2 \
+ --hash=sha256:2c2349112351b88699d8d4b6b075022c0808887cb7ad10069318a8b0bc88db44 \
+ --hash=sha256:5dbbc68b317e5e42f327f9021763545dc3fc3bfe22e6deb96aaf1fc38874156a
+ # via flask
+jinja2==3.1.2 \
+ --hash=sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852 \
+ --hash=sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61
+ # via flask
+markupsafe==2.1.1 \
+ --hash=sha256:0212a68688482dc52b2d45013df70d169f542b7394fc744c02a57374a4207003 \
+ --hash=sha256:089cf3dbf0cd6c100f02945abeb18484bd1ee57a079aefd52cffd17fba910b88 \
+ --hash=sha256:10c1bfff05d95783da83491be968e8fe789263689c02724e0c691933c52994f5 \
+ --hash=sha256:33b74d289bd2f5e527beadcaa3f401e0df0a89927c1559c8566c066fa4248ab7 \
+ --hash=sha256:3799351e2336dc91ea70b034983ee71cf2f9533cdff7c14c90ea126bfd95d65a \
+ --hash=sha256:3ce11ee3f23f79dbd06fb3d63e2f6af7b12db1d46932fe7bd8afa259a5996603 \
+ --hash=sha256:421be9fbf0ffe9ffd7a378aafebbf6f4602d564d34be190fc19a193232fd12b1 \
+ --hash=sha256:43093fb83d8343aac0b1baa75516da6092f58f41200907ef92448ecab8825135 \
+ --hash=sha256:46d00d6cfecdde84d40e572d63735ef81423ad31184100411e6e3388d405e247 \
+ --hash=sha256:4a33dea2b688b3190ee12bd7cfa29d39c9ed176bda40bfa11099a3ce5d3a7ac6 \
+ --hash=sha256:4b9fe39a2ccc108a4accc2676e77da025ce383c108593d65cc909add5c3bd601 \
+ --hash=sha256:56442863ed2b06d19c37f94d999035e15ee982988920e12a5b4ba29b62ad1f77 \
+ --hash=sha256:671cd1187ed5e62818414afe79ed29da836dde67166a9fac6d435873c44fdd02 \
+ --hash=sha256:694deca8d702d5db21ec83983ce0bb4b26a578e71fbdbd4fdcd387daa90e4d5e \
+ --hash=sha256:6a074d34ee7a5ce3effbc526b7083ec9731bb3cbf921bbe1d3005d4d2bdb3a63 \
+ --hash=sha256:6d0072fea50feec76a4c418096652f2c3238eaa014b2f94aeb1d56a66b41403f \
+ --hash=sha256:6fbf47b5d3728c6aea2abb0589b5d30459e369baa772e0f37a0320185e87c980 \
+ --hash=sha256:7f91197cc9e48f989d12e4e6fbc46495c446636dfc81b9ccf50bb0ec74b91d4b \
+ --hash=sha256:86b1f75c4e7c2ac2ccdaec2b9022845dbb81880ca318bb7a0a01fbf7813e3812 \
+ --hash=sha256:8dc1c72a69aa7e082593c4a203dcf94ddb74bb5c8a731e4e1eb68d031e8498ff \
+ --hash=sha256:8e3dcf21f367459434c18e71b2a9532d96547aef8a871872a5bd69a715c15f96 \
+ --hash=sha256:8e576a51ad59e4bfaac456023a78f6b5e6e7651dcd383bcc3e18d06f9b55d6d1 \
+ --hash=sha256:96e37a3dc86e80bf81758c152fe66dbf60ed5eca3d26305edf01892257049925 \
+ --hash=sha256:97a68e6ada378df82bc9f16b800ab77cbf4b2fada0081794318520138c088e4a \
+ --hash=sha256:99a2a507ed3ac881b975a2976d59f38c19386d128e7a9a18b7df6fff1fd4c1d6 \
+ --hash=sha256:a49907dd8420c5685cfa064a1335b6754b74541bbb3706c259c02ed65b644b3e \
+ --hash=sha256:b09bf97215625a311f669476f44b8b318b075847b49316d3e28c08e41a7a573f \
+ --hash=sha256:b7bd98b796e2b6553da7225aeb61f447f80a1ca64f41d83612e6139ca5213aa4 \
+ --hash=sha256:b87db4360013327109564f0e591bd2a3b318547bcef31b468a92ee504d07ae4f \
+ --hash=sha256:bcb3ed405ed3222f9904899563d6fc492ff75cce56cba05e32eff40e6acbeaa3 \
+ --hash=sha256:d4306c36ca495956b6d568d276ac11fdd9c30a36f1b6eb928070dc5360b22e1c \
+ --hash=sha256:d5ee4f386140395a2c818d149221149c54849dfcfcb9f1debfe07a8b8bd63f9a \
+ --hash=sha256:dda30ba7e87fbbb7eab1ec9f58678558fd9a6b8b853530e176eabd064da81417 \
+ --hash=sha256:e04e26803c9c3851c931eac40c695602c6295b8d432cbe78609649ad9bd2da8a \
+ --hash=sha256:e1c0b87e09fa55a220f058d1d49d3fb8df88fbfab58558f1198e08c1e1de842a \
+ --hash=sha256:e72591e9ecd94d7feb70c1cbd7be7b3ebea3f548870aa91e2732960fa4d57a37 \
+ --hash=sha256:e8c843bbcda3a2f1e3c2ab25913c80a3c5376cd00c6e8c4a86a89a28c8dc5452 \
+ --hash=sha256:efc1913fd2ca4f334418481c7e595c00aad186563bbc1ec76067848c7ca0a933 \
+ --hash=sha256:f121a1420d4e173a5d96e47e9a0c0dcff965afdf1626d28de1460815f7c4ee7a \
+ --hash=sha256:fc7b548b17d238737688817ab67deebb30e8073c95749d55538ed473130ec0c7
+ # via
+ # jinja2
+ # werkzeug
+werkzeug==2.2.2 \
+ --hash=sha256:7ea2d48322cc7c0f8b3a215ed73eabd7b5d75d0b50e31ab006286ccff9e00b8f \
+ --hash=sha256:f979ab81f58d7318e064e99c4506445d60135ac5cd2e177a2de0089bfd4c9bd5
+ # via flask
+zipp==3.11.0 \
+ --hash=sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa \
+ --hash=sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766
+ # via importlib-metadata
diff --git a/examples/bzlmod/.bazelignore b/examples/bzlmod/.bazelignore
new file mode 100644
index 0000000..ab3eb16
--- /dev/null
+++ b/examples/bzlmod/.bazelignore
@@ -0,0 +1 @@
+other_module
diff --git a/examples/bzlmod/.bazelrc b/examples/bzlmod/.bazelrc
new file mode 100644
index 0000000..6f557e6
--- /dev/null
+++ b/examples/bzlmod/.bazelrc
@@ -0,0 +1,10 @@
+common --experimental_enable_bzlmod
+
+coverage --java_runtime_version=remotejdk_11
+
+test --test_output=errors --enable_runfiles
+
+# Windows requires these for multi-python support:
+build --enable_runfiles
+
+startup --windows_enable_symlinks
diff --git a/examples/bzlmod/.bazelversion b/examples/bzlmod/.bazelversion
new file mode 100644
index 0000000..09b254e
--- /dev/null
+++ b/examples/bzlmod/.bazelversion
@@ -0,0 +1 @@
+6.0.0
diff --git a/examples/bzlmod/.gitignore b/examples/bzlmod/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/examples/bzlmod/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/examples/bzlmod/BUILD.bazel b/examples/bzlmod/BUILD.bazel
new file mode 100644
index 0000000..3db7751
--- /dev/null
+++ b/examples/bzlmod/BUILD.bazel
@@ -0,0 +1,94 @@
+# Load various rules so that we can have bazel download
+# various rulesets and dependencies.
+# The `load` statement imports the symbol for the rule, in the defined
+# ruleset. When the symbol is loaded you can use the rule.
+
+# The names @pip and @python_39 are values that are repository
+# names. Those names are defined in the MODULES.bazel file.
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+load("@pip//:requirements.bzl", "all_data_requirements", "all_requirements", "all_whl_requirements", "requirement")
+load("@python_3_9//:defs.bzl", py_test_with_transition = "py_test")
+load("@python_versions//3.10:defs.bzl", compile_pip_requirements_3_10 = "compile_pip_requirements")
+load("@python_versions//3.9:defs.bzl", compile_pip_requirements_3_9 = "compile_pip_requirements")
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+
+# This stanza calls a rule that generates targets for managing pip dependencies
+# with pip-compile.
+compile_pip_requirements_3_9(
+ name = "requirements_3_9",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock_3_9.txt",
+ requirements_windows = "requirements_windows_3_9.txt",
+)
+
+# This stanza calls a rule that generates targets for managing pip dependencies
+# with pip-compile.
+compile_pip_requirements_3_10(
+ name = "requirements_3_10",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock_3_10.txt",
+ requirements_windows = "requirements_windows_3_10.txt",
+)
+
+# The rules below are language specific rules defined in
+# rules_python. See
+# https://bazel.build/reference/be/python
+
+# see https://bazel.build/reference/be/python#py_library
+py_library(
+ name = "lib",
+ srcs = ["lib.py"],
+ deps = [
+ requirement("pylint"),
+ requirement("tabulate"),
+ requirement("python-dateutil"),
+ ],
+)
+
+# see https://bazel.build/reference/be/python#py_binary
+py_binary(
+ name = "bzlmod",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":lib",
+ ],
+)
+
+# see https://bazel.build/reference/be/python#py_test
+py_test(
+ name = "test",
+ srcs = ["test.py"],
+ main = "test.py",
+ deps = [":lib"],
+)
+
+py_test_with_transition(
+ name = "test_with_transition",
+ srcs = ["test.py"],
+ main = "test.py",
+ deps = [":lib"],
+)
+
+# This example is also used for integration tests within
+# rules_python. We are using
+# https://github.com/bazelbuild/bazel-skylib
+# to run some of the tests.
+# See: https://github.com/bazelbuild/bazel-skylib/blob/main/docs/build_test_doc.md
+build_test(
+ name = "all_wheels",
+ targets = all_whl_requirements,
+)
+
+build_test(
+ name = "all_data_requirements",
+ targets = all_data_requirements,
+)
+
+build_test(
+ name = "all_requirements",
+ targets = all_requirements,
+)
diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel
new file mode 100644
index 0000000..df88ae8
--- /dev/null
+++ b/examples/bzlmod/MODULE.bazel
@@ -0,0 +1,130 @@
+module(
+ name = "example_bzlmod",
+ version = "0.0.0",
+ compatibility_level = 1,
+)
+
+bazel_dep(name = "bazel_skylib", version = "1.4.1")
+bazel_dep(name = "rules_python", version = "0.0.0")
+local_path_override(
+ module_name = "rules_python",
+ path = "../..",
+)
+
+# Setting python.toolchain is optional as rules_python
+# sets a toolchain for you, using the latest supported version
+# of Python. We do recomend that you set a version here.
+
+# We next initialize the python toolchain using the extension.
+# You can set different Python versions in this block.
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
+python.toolchain(
+ configure_coverage_tool = True,
+ # Only set when you have mulitple toolchain versions.
+ is_default = True,
+ python_version = "3.9",
+)
+
+# We are also using a second version of Python in this project.
+# Typically you will only need a single version of Python, but
+# If you need a different vesion we support more than one.
+# Note: we do not supporting using multiple pip extensions, this is
+# work in progress.
+python.toolchain(
+ configure_coverage_tool = True,
+ python_version = "3.10",
+)
+
+# You only need to load this repositories if you are using multiple Python versions.
+# See the tests folder for various examples on using multiple Python versions.
+# The names "python_3_9" and "python_3_10" are autmatically created by the repo
+# rules based on the `python_version` arg values.
+use_repo(python, "python_3_10", "python_3_9", "python_versions")
+
+# This extension allows a user to create modifications to how rules_python
+# creates different wheel repositories. Different attributes allow the user
+# to modify the BUILD file, and copy files.
+# See @rules_python//python/extensions:whl_mods.bzl attributes for more information
+# on each of the attributes.
+# You are able to set a hub name, so that you can have different modifications of the same
+# wheel in different pip hubs.
+pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
+
+# Call whl_mods.create for the requests package.
+pip.whl_mods(
+ # we are using the appended_build_content.BUILD file
+ # to add content to the request wheel BUILD file.
+ additive_build_content_file = "//whl_mods:appended_build_content.BUILD",
+ data = [":generated_file"],
+ hub_name = "whl_mods_hub",
+ whl_name = "requests",
+)
+
+ADDITIVE_BUILD_CONTENT = """\
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+write_file(
+ name = "generated_file",
+ out = "generated_file.txt",
+ content = ["Hello world from build content file"],
+)
+"""
+
+# Call whl_mods.create for the wheel package.
+pip.whl_mods(
+ additive_build_content = ADDITIVE_BUILD_CONTENT,
+ copy_executables = {
+ "@@//whl_mods:data/copy_executable.py": "copied_content/executable.py",
+ },
+ copy_files = {
+ "@@//whl_mods:data/copy_file.txt": "copied_content/file.txt",
+ },
+ data = [":generated_file"],
+ data_exclude_glob = ["site-packages/*.dist-info/WHEEL"],
+ hub_name = "whl_mods_hub",
+ whl_name = "wheel",
+)
+use_repo(pip, "whl_mods_hub")
+
+# To fetch pip dependencies, use pip.parse. We can pass in various options,
+# but typically we pass requirements and the Python version. The Python
+# version must have been configured by a corresponding `python.toolchain()`
+# call.
+# Alternatively, `python_interpreter_target` can be used to directly specify
+# the Python interpreter to run to resolve dependencies.
+# Because we do not have a python_version defined here
+# pip.parse uses the python toolchain that is set as default.
+pip.parse(
+ hub_name = "pip",
+ requirements_lock = "//:requirements_lock_3_9.txt",
+ requirements_windows = "//:requirements_windows_3_9.txt",
+ # These modifications were created above and we
+ # are providing pip.parse with the label of the mod
+ # and the name of the wheel.
+ whl_modifications = {
+ "@whl_mods_hub//:requests.json": "requests",
+ "@whl_mods_hub//:wheel.json": "wheel",
+ },
+)
+pip.parse(
+ hub_name = "pip",
+ python_version = "3.10",
+ requirements_lock = "//:requirements_lock_3_10.txt",
+ requirements_windows = "//:requirements_windows_3_10.txt",
+ # These modifications were created above and we
+ # are providing pip.parse with the label of the mod
+ # and the name of the wheel.
+ whl_modifications = {
+ "@whl_mods_hub//:requests.json": "requests",
+ "@whl_mods_hub//:wheel.json": "wheel",
+ },
+)
+
+# NOTE: The pip_39 repo is only used because the plain `@pip` repo doesn't
+# yet support entry points; see https://github.com/bazelbuild/rules_python/issues/1262
+use_repo(pip, "pip", "pip_39")
+
+bazel_dep(name = "other_module", version = "", repo_name = "our_other_module")
+local_path_override(
+ module_name = "other_module",
+ path = "other_module",
+)
diff --git a/examples/bzlmod/WORKSPACE b/examples/bzlmod/WORKSPACE
new file mode 100644
index 0000000..78cc252
--- /dev/null
+++ b/examples/bzlmod/WORKSPACE
@@ -0,0 +1,2 @@
+# Empty file indicating the root of a Bazel workspace.
+# Dependencies and setup are in MODULE.bazel.
diff --git a/examples/bzlmod/__main__.py b/examples/bzlmod/__main__.py
new file mode 100644
index 0000000..daf1749
--- /dev/null
+++ b/examples/bzlmod/__main__.py
@@ -0,0 +1,20 @@
+# 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.
+
+from lib import main
+import sys
+
+if __name__ == "__main__":
+ print(main([["A", 1], ["B", 2]]))
+ print(sys.version)
diff --git a/examples/bzlmod/description.md b/examples/bzlmod/description.md
new file mode 100644
index 0000000..a5e5fba
--- /dev/null
+++ b/examples/bzlmod/description.md
@@ -0,0 +1,10 @@
+Before this PR the `coverage_tool` automatically registered by `rules_python`
+was visible outside the toolchain repository. This fixes it to be consistent
+with `non-bzlmod` setups and ensures that the default `coverage_tool` is not
+visible outside the toolchain repos.
+
+This means that the `MODULE.bazel` file can be cleaned-up at the expense of
+relaxing the `coverage_tool` attribute for the `python_repository` to be a
+simple string as the label would be evaluated within the context of
+`rules_python` which may not necessarily resolve correctly without the
+`use_repo` statement in our `MODULE.bazel`.
diff --git a/examples/bzlmod/entry_point/BUILD.bazel b/examples/bzlmod/entry_point/BUILD.bazel
new file mode 100644
index 0000000..f68552c
--- /dev/null
+++ b/examples/bzlmod/entry_point/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@pip_39//:requirements.bzl", "entry_point")
+load("@rules_python//python:defs.bzl", "py_test")
+
+alias(
+ name = "yamllint",
+ actual = entry_point("yamllint"),
+)
+
+py_test(
+ name = "entry_point_test",
+ srcs = ["test_entry_point.py"],
+ data = [
+ ":yamllint",
+ ],
+ env = {
+ "YAMLLINT_ENTRY_POINT": "$(rlocationpath :yamllint)",
+ },
+ main = "test_entry_point.py",
+ deps = ["@rules_python//python/runfiles"],
+)
diff --git a/examples/bzlmod/entry_point/test_entry_point.py b/examples/bzlmod/entry_point/test_entry_point.py
new file mode 100644
index 0000000..5a37458
--- /dev/null
+++ b/examples/bzlmod/entry_point/test_entry_point.py
@@ -0,0 +1,43 @@
+# 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.
+
+import os
+import pathlib
+import subprocess
+import unittest
+
+from python.runfiles import runfiles
+
+
+class ExampleTest(unittest.TestCase):
+ def test_entry_point(self):
+ rlocation_path = os.environ.get("YAMLLINT_ENTRY_POINT")
+ assert (
+ rlocation_path is not None
+ ), "expected 'YAMLLINT_ENTRY_POINT' env variable to be set to rlocation of the tool"
+
+ entry_point = pathlib.Path(runfiles.Create().Rlocation(rlocation_path))
+ self.assertTrue(entry_point.exists(), f"'{entry_point}' does not exist")
+
+ proc = subprocess.run(
+ [str(entry_point), "--version"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.28.0")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/bzlmod/gazelle_python.yaml b/examples/bzlmod/gazelle_python.yaml
new file mode 100644
index 0000000..12096e5
--- /dev/null
+++ b/examples/bzlmod/gazelle_python.yaml
@@ -0,0 +1,590 @@
+# GENERATED FILE - DO NOT EDIT!
+#
+# To update this file, run:
+# bazel run //:gazelle_python_manifest.update
+
+manifest:
+ modules_mapping:
+ S3: s3cmd
+ S3.ACL: s3cmd
+ S3.AccessLog: s3cmd
+ S3.BidirMap: s3cmd
+ S3.CloudFront: s3cmd
+ S3.Config: s3cmd
+ S3.ConnMan: s3cmd
+ S3.Crypto: s3cmd
+ S3.Custom_httplib27: s3cmd
+ S3.Custom_httplib3x: s3cmd
+ S3.Exceptions: s3cmd
+ S3.ExitCodes: s3cmd
+ S3.FileDict: s3cmd
+ S3.FileLists: s3cmd
+ S3.HashCache: s3cmd
+ S3.MultiPart: s3cmd
+ S3.PkgInfo: s3cmd
+ S3.Progress: s3cmd
+ S3.S3: s3cmd
+ S3.S3Uri: s3cmd
+ S3.SortedDict: s3cmd
+ S3.Utils: s3cmd
+ astroid: astroid
+ astroid.arguments: astroid
+ astroid.astroid_manager: astroid
+ astroid.bases: astroid
+ astroid.brain: astroid
+ astroid.brain.brain_argparse: astroid
+ astroid.brain.brain_attrs: astroid
+ astroid.brain.brain_boto3: astroid
+ astroid.brain.brain_builtin_inference: astroid
+ astroid.brain.brain_collections: astroid
+ astroid.brain.brain_crypt: astroid
+ astroid.brain.brain_ctypes: astroid
+ astroid.brain.brain_curses: astroid
+ astroid.brain.brain_dataclasses: astroid
+ astroid.brain.brain_dateutil: astroid
+ astroid.brain.brain_fstrings: astroid
+ astroid.brain.brain_functools: astroid
+ astroid.brain.brain_gi: astroid
+ astroid.brain.brain_hashlib: astroid
+ astroid.brain.brain_http: astroid
+ astroid.brain.brain_hypothesis: astroid
+ astroid.brain.brain_io: astroid
+ astroid.brain.brain_mechanize: astroid
+ astroid.brain.brain_multiprocessing: astroid
+ astroid.brain.brain_namedtuple_enum: astroid
+ astroid.brain.brain_nose: astroid
+ astroid.brain.brain_numpy_core_einsumfunc: astroid
+ astroid.brain.brain_numpy_core_fromnumeric: astroid
+ astroid.brain.brain_numpy_core_function_base: astroid
+ astroid.brain.brain_numpy_core_multiarray: astroid
+ astroid.brain.brain_numpy_core_numeric: astroid
+ astroid.brain.brain_numpy_core_numerictypes: astroid
+ astroid.brain.brain_numpy_core_umath: astroid
+ astroid.brain.brain_numpy_ma: astroid
+ astroid.brain.brain_numpy_ndarray: astroid
+ astroid.brain.brain_numpy_random_mtrand: astroid
+ astroid.brain.brain_numpy_utils: astroid
+ astroid.brain.brain_pathlib: astroid
+ astroid.brain.brain_pkg_resources: astroid
+ astroid.brain.brain_pytest: astroid
+ astroid.brain.brain_qt: astroid
+ astroid.brain.brain_random: astroid
+ astroid.brain.brain_re: astroid
+ astroid.brain.brain_responses: astroid
+ astroid.brain.brain_scipy_signal: astroid
+ astroid.brain.brain_signal: astroid
+ astroid.brain.brain_six: astroid
+ astroid.brain.brain_sqlalchemy: astroid
+ astroid.brain.brain_ssl: astroid
+ astroid.brain.brain_subprocess: astroid
+ astroid.brain.brain_threading: astroid
+ astroid.brain.brain_type: astroid
+ astroid.brain.brain_typing: astroid
+ astroid.brain.brain_unittest: astroid
+ astroid.brain.brain_uuid: astroid
+ astroid.brain.helpers: astroid
+ astroid.builder: astroid
+ astroid.const: astroid
+ astroid.context: astroid
+ astroid.decorators: astroid
+ astroid.exceptions: astroid
+ astroid.filter_statements: astroid
+ astroid.helpers: astroid
+ astroid.inference: astroid
+ astroid.inference_tip: astroid
+ astroid.interpreter: astroid
+ astroid.interpreter.dunder_lookup: astroid
+ astroid.interpreter.objectmodel: astroid
+ astroid.manager: astroid
+ astroid.mixins: astroid
+ astroid.modutils: astroid
+ astroid.node_classes: astroid
+ astroid.nodes: astroid
+ astroid.nodes.as_string: astroid
+ astroid.nodes.const: astroid
+ astroid.nodes.node_classes: astroid
+ astroid.nodes.node_ng: astroid
+ astroid.nodes.scoped_nodes: astroid
+ astroid.nodes.scoped_nodes.mixin: astroid
+ astroid.nodes.scoped_nodes.scoped_nodes: astroid
+ astroid.nodes.scoped_nodes.utils: astroid
+ astroid.nodes.utils: astroid
+ astroid.objects: astroid
+ astroid.protocols: astroid
+ astroid.raw_building: astroid
+ astroid.rebuilder: astroid
+ astroid.scoped_nodes: astroid
+ astroid.test_utils: astroid
+ astroid.transforms: astroid
+ astroid.typing: astroid
+ astroid.util: astroid
+ certifi: certifi
+ certifi.core: certifi
+ chardet: chardet
+ chardet.big5freq: chardet
+ chardet.big5prober: chardet
+ chardet.chardistribution: chardet
+ chardet.charsetgroupprober: chardet
+ chardet.charsetprober: chardet
+ chardet.cli: chardet
+ chardet.cli.chardetect: chardet
+ chardet.codingstatemachine: chardet
+ chardet.compat: chardet
+ chardet.cp949prober: chardet
+ chardet.enums: chardet
+ chardet.escprober: chardet
+ chardet.escsm: chardet
+ chardet.eucjpprober: chardet
+ chardet.euckrfreq: chardet
+ chardet.euckrprober: chardet
+ chardet.euctwfreq: chardet
+ chardet.euctwprober: chardet
+ chardet.gb2312freq: chardet
+ chardet.gb2312prober: chardet
+ chardet.hebrewprober: chardet
+ chardet.jisfreq: chardet
+ chardet.jpcntx: chardet
+ chardet.langbulgarianmodel: chardet
+ chardet.langgreekmodel: chardet
+ chardet.langhebrewmodel: chardet
+ chardet.langhungarianmodel: chardet
+ chardet.langrussianmodel: chardet
+ chardet.langthaimodel: chardet
+ chardet.langturkishmodel: chardet
+ chardet.latin1prober: chardet
+ chardet.mbcharsetprober: chardet
+ chardet.mbcsgroupprober: chardet
+ chardet.mbcssm: chardet
+ chardet.metadata: chardet
+ chardet.metadata.languages: chardet
+ chardet.sbcharsetprober: chardet
+ chardet.sbcsgroupprober: chardet
+ chardet.sjisprober: chardet
+ chardet.universaldetector: chardet
+ chardet.utf8prober: chardet
+ chardet.version: chardet
+ dateutil: python_dateutil
+ dateutil.easter: python_dateutil
+ dateutil.parser: python_dateutil
+ dateutil.parser.isoparser: python_dateutil
+ dateutil.relativedelta: python_dateutil
+ dateutil.rrule: python_dateutil
+ dateutil.tz: python_dateutil
+ dateutil.tz.tz: python_dateutil
+ dateutil.tz.win: python_dateutil
+ dateutil.tzwin: python_dateutil
+ dateutil.utils: python_dateutil
+ dateutil.zoneinfo: python_dateutil
+ dateutil.zoneinfo.rebuild: python_dateutil
+ dill: dill
+ dill.detect: dill
+ dill.logger: dill
+ dill.objtypes: dill
+ dill.pointers: dill
+ dill.session: dill
+ dill.settings: dill
+ dill.source: dill
+ dill.temp: dill
+ idna: idna
+ idna.codec: idna
+ idna.compat: idna
+ idna.core: idna
+ idna.idnadata: idna
+ idna.intranges: idna
+ idna.package_data: idna
+ idna.uts46data: idna
+ isort: isort
+ isort.api: isort
+ isort.comments: isort
+ isort.core: isort
+ isort.deprecated: isort
+ isort.deprecated.finders: isort
+ isort.exceptions: isort
+ isort.files: isort
+ isort.format: isort
+ isort.hooks: isort
+ isort.identify: isort
+ isort.io: isort
+ isort.literal: isort
+ isort.logo: isort
+ isort.main: isort
+ isort.output: isort
+ isort.parse: isort
+ isort.place: isort
+ isort.profiles: isort
+ isort.pylama_isort: isort
+ isort.sections: isort
+ isort.settings: isort
+ isort.setuptools_commands: isort
+ isort.sorting: isort
+ isort.stdlibs: isort
+ isort.stdlibs.all: isort
+ isort.stdlibs.py2: isort
+ isort.stdlibs.py27: isort
+ isort.stdlibs.py3: isort
+ isort.stdlibs.py310: isort
+ isort.stdlibs.py311: isort
+ isort.stdlibs.py36: isort
+ isort.stdlibs.py37: isort
+ isort.stdlibs.py38: isort
+ isort.stdlibs.py39: isort
+ isort.utils: isort
+ isort.wrap: isort
+ isort.wrap_modes: isort
+ lazy_object_proxy: lazy_object_proxy
+ lazy_object_proxy.compat: lazy_object_proxy
+ lazy_object_proxy.simple: lazy_object_proxy
+ lazy_object_proxy.slots: lazy_object_proxy
+ lazy_object_proxy.utils: lazy_object_proxy
+ magic: python_magic
+ magic.compat: python_magic
+ magic.loader: python_magic
+ mccabe: mccabe
+ pathspec: pathspec
+ pathspec.gitignore: pathspec
+ pathspec.pathspec: pathspec
+ pathspec.pattern: pathspec
+ pathspec.patterns: pathspec
+ pathspec.patterns.gitwildmatch: pathspec
+ pathspec.util: pathspec
+ pkg_resources: setuptools
+ pkg_resources.extern: setuptools
+ platformdirs: platformdirs
+ platformdirs.android: platformdirs
+ platformdirs.api: platformdirs
+ platformdirs.macos: platformdirs
+ platformdirs.unix: platformdirs
+ platformdirs.version: platformdirs
+ platformdirs.windows: platformdirs
+ pylint: pylint
+ pylint.checkers: pylint
+ pylint.checkers.async: pylint
+ pylint.checkers.base: pylint
+ pylint.checkers.base.basic_checker: pylint
+ pylint.checkers.base.basic_error_checker: pylint
+ pylint.checkers.base.comparison_checker: pylint
+ pylint.checkers.base.docstring_checker: pylint
+ pylint.checkers.base.name_checker: pylint
+ pylint.checkers.base.name_checker.checker: pylint
+ pylint.checkers.base.name_checker.naming_style: pylint
+ pylint.checkers.base.pass_checker: pylint
+ pylint.checkers.base_checker: pylint
+ pylint.checkers.classes: pylint
+ pylint.checkers.classes.class_checker: pylint
+ pylint.checkers.classes.special_methods_checker: pylint
+ pylint.checkers.deprecated: pylint
+ pylint.checkers.design_analysis: pylint
+ pylint.checkers.dunder_methods: pylint
+ pylint.checkers.ellipsis_checker: pylint
+ pylint.checkers.exceptions: pylint
+ pylint.checkers.format: pylint
+ pylint.checkers.imports: pylint
+ pylint.checkers.lambda_expressions: pylint
+ pylint.checkers.logging: pylint
+ pylint.checkers.mapreduce_checker: pylint
+ pylint.checkers.method_args: pylint
+ pylint.checkers.misc: pylint
+ pylint.checkers.modified_iterating_checker: pylint
+ pylint.checkers.newstyle: pylint
+ pylint.checkers.non_ascii_names: pylint
+ pylint.checkers.raw_metrics: pylint
+ pylint.checkers.refactoring: pylint
+ pylint.checkers.refactoring.implicit_booleaness_checker: pylint
+ pylint.checkers.refactoring.not_checker: pylint
+ pylint.checkers.refactoring.recommendation_checker: pylint
+ pylint.checkers.refactoring.refactoring_checker: pylint
+ pylint.checkers.similar: pylint
+ pylint.checkers.spelling: pylint
+ pylint.checkers.stdlib: pylint
+ pylint.checkers.strings: pylint
+ pylint.checkers.threading_checker: pylint
+ pylint.checkers.typecheck: pylint
+ pylint.checkers.unicode: pylint
+ pylint.checkers.unsupported_version: pylint
+ pylint.checkers.utils: pylint
+ pylint.checkers.variables: pylint
+ pylint.config: pylint
+ pylint.config.argument: pylint
+ pylint.config.arguments_manager: pylint
+ pylint.config.arguments_provider: pylint
+ pylint.config.callback_actions: pylint
+ pylint.config.config_file_parser: pylint
+ pylint.config.config_initialization: pylint
+ pylint.config.configuration_mixin: pylint
+ pylint.config.deprecation_actions: pylint
+ pylint.config.environment_variable: pylint
+ pylint.config.exceptions: pylint
+ pylint.config.find_default_config_files: pylint
+ pylint.config.help_formatter: pylint
+ pylint.config.option: pylint
+ pylint.config.option_manager_mixin: pylint
+ pylint.config.option_parser: pylint
+ pylint.config.options_provider_mixin: pylint
+ pylint.config.utils: pylint
+ pylint.constants: pylint
+ pylint.epylint: pylint
+ pylint.exceptions: pylint
+ pylint.extensions: pylint
+ pylint.extensions.bad_builtin: pylint
+ pylint.extensions.broad_try_clause: pylint
+ pylint.extensions.check_elif: pylint
+ pylint.extensions.code_style: pylint
+ pylint.extensions.comparetozero: pylint
+ pylint.extensions.comparison_placement: pylint
+ pylint.extensions.confusing_elif: pylint
+ pylint.extensions.consider_ternary_expression: pylint
+ pylint.extensions.docparams: pylint
+ pylint.extensions.docstyle: pylint
+ pylint.extensions.empty_comment: pylint
+ pylint.extensions.emptystring: pylint
+ pylint.extensions.eq_without_hash: pylint
+ pylint.extensions.for_any_all: pylint
+ pylint.extensions.mccabe: pylint
+ pylint.extensions.no_self_use: pylint
+ pylint.extensions.overlapping_exceptions: pylint
+ pylint.extensions.private_import: pylint
+ pylint.extensions.redefined_loop_name: pylint
+ pylint.extensions.redefined_variable_type: pylint
+ pylint.extensions.set_membership: pylint
+ pylint.extensions.typing: pylint
+ pylint.extensions.while_used: pylint
+ pylint.graph: pylint
+ pylint.interfaces: pylint
+ pylint.lint: pylint
+ pylint.lint.base_options: pylint
+ pylint.lint.caching: pylint
+ pylint.lint.expand_modules: pylint
+ pylint.lint.message_state_handler: pylint
+ pylint.lint.parallel: pylint
+ pylint.lint.pylinter: pylint
+ pylint.lint.report_functions: pylint
+ pylint.lint.run: pylint
+ pylint.lint.utils: pylint
+ pylint.message: pylint
+ pylint.message.message: pylint
+ pylint.message.message_definition: pylint
+ pylint.message.message_definition_store: pylint
+ pylint.message.message_id_store: pylint
+ pylint.pyreverse: pylint
+ pylint.pyreverse.diadefslib: pylint
+ pylint.pyreverse.diagrams: pylint
+ pylint.pyreverse.dot_printer: pylint
+ pylint.pyreverse.inspector: pylint
+ pylint.pyreverse.main: pylint
+ pylint.pyreverse.mermaidjs_printer: pylint
+ pylint.pyreverse.plantuml_printer: pylint
+ pylint.pyreverse.printer: pylint
+ pylint.pyreverse.printer_factory: pylint
+ pylint.pyreverse.utils: pylint
+ pylint.pyreverse.vcg_printer: pylint
+ pylint.pyreverse.writer: pylint
+ pylint.reporters: pylint
+ pylint.reporters.base_reporter: pylint
+ pylint.reporters.collecting_reporter: pylint
+ pylint.reporters.json_reporter: pylint
+ pylint.reporters.multi_reporter: pylint
+ pylint.reporters.reports_handler_mix_in: pylint
+ pylint.reporters.text: pylint
+ pylint.reporters.ureports: pylint
+ pylint.reporters.ureports.base_writer: pylint
+ pylint.reporters.ureports.nodes: pylint
+ pylint.reporters.ureports.text_writer: pylint
+ pylint.testutils: pylint
+ pylint.testutils.checker_test_case: pylint
+ pylint.testutils.configuration_test: pylint
+ pylint.testutils.constants: pylint
+ pylint.testutils.decorator: pylint
+ pylint.testutils.functional: pylint
+ pylint.testutils.functional.find_functional_tests: pylint
+ pylint.testutils.functional.lint_module_output_update: pylint
+ pylint.testutils.functional.test_file: pylint
+ pylint.testutils.functional_test_file: pylint
+ pylint.testutils.get_test_info: pylint
+ pylint.testutils.global_test_linter: pylint
+ pylint.testutils.lint_module_test: pylint
+ pylint.testutils.output_line: pylint
+ pylint.testutils.pyreverse: pylint
+ pylint.testutils.reporter_for_tests: pylint
+ pylint.testutils.tokenize_str: pylint
+ pylint.testutils.unittest_linter: pylint
+ pylint.testutils.utils: pylint
+ pylint.typing: pylint
+ pylint.utils: pylint
+ pylint.utils.ast_walker: pylint
+ pylint.utils.docs: pylint
+ pylint.utils.file_state: pylint
+ pylint.utils.linterstats: pylint
+ pylint.utils.pragma_parser: pylint
+ pylint.utils.utils: pylint
+ requests: requests
+ requests.adapters: requests
+ requests.api: requests
+ requests.auth: requests
+ requests.certs: requests
+ requests.compat: requests
+ requests.cookies: requests
+ requests.exceptions: requests
+ requests.help: requests
+ requests.hooks: requests
+ requests.models: requests
+ requests.packages: requests
+ requests.sessions: requests
+ requests.status_codes: requests
+ requests.structures: requests
+ requests.utils: requests
+ setuptools: setuptools
+ setuptools.archive_util: setuptools
+ setuptools.build_meta: setuptools
+ setuptools.command: setuptools
+ setuptools.command.alias: setuptools
+ setuptools.command.bdist_egg: setuptools
+ setuptools.command.bdist_rpm: setuptools
+ setuptools.command.build: setuptools
+ setuptools.command.build_clib: setuptools
+ setuptools.command.build_ext: setuptools
+ setuptools.command.build_py: setuptools
+ setuptools.command.develop: setuptools
+ setuptools.command.dist_info: setuptools
+ setuptools.command.easy_install: setuptools
+ setuptools.command.editable_wheel: setuptools
+ setuptools.command.egg_info: setuptools
+ setuptools.command.install: setuptools
+ setuptools.command.install_egg_info: setuptools
+ setuptools.command.install_lib: setuptools
+ setuptools.command.install_scripts: setuptools
+ setuptools.command.py36compat: setuptools
+ setuptools.command.register: setuptools
+ setuptools.command.rotate: setuptools
+ setuptools.command.saveopts: setuptools
+ setuptools.command.sdist: setuptools
+ setuptools.command.setopt: setuptools
+ setuptools.command.test: setuptools
+ setuptools.command.upload: setuptools
+ setuptools.command.upload_docs: setuptools
+ setuptools.config: setuptools
+ setuptools.config.expand: setuptools
+ setuptools.config.pyprojecttoml: setuptools
+ setuptools.config.setupcfg: setuptools
+ setuptools.dep_util: setuptools
+ setuptools.depends: setuptools
+ setuptools.discovery: setuptools
+ setuptools.dist: setuptools
+ setuptools.errors: setuptools
+ setuptools.extension: setuptools
+ setuptools.extern: setuptools
+ setuptools.glob: setuptools
+ setuptools.installer: setuptools
+ setuptools.launch: setuptools
+ setuptools.logging: setuptools
+ setuptools.monkey: setuptools
+ setuptools.msvc: setuptools
+ setuptools.namespaces: setuptools
+ setuptools.package_index: setuptools
+ setuptools.py34compat: setuptools
+ setuptools.sandbox: setuptools
+ setuptools.unicode_utils: setuptools
+ setuptools.version: setuptools
+ setuptools.wheel: setuptools
+ setuptools.windows_support: setuptools
+ six: six
+ tabulate: tabulate
+ tabulate.version: tabulate
+ tomli: tomli
+ tomlkit: tomlkit
+ tomlkit.api: tomlkit
+ tomlkit.container: tomlkit
+ tomlkit.exceptions: tomlkit
+ tomlkit.items: tomlkit
+ tomlkit.parser: tomlkit
+ tomlkit.source: tomlkit
+ tomlkit.toml_char: tomlkit
+ tomlkit.toml_document: tomlkit
+ tomlkit.toml_file: tomlkit
+ typing_extensions: typing_extensions
+ urllib3: urllib3
+ urllib3.connection: urllib3
+ urllib3.connectionpool: urllib3
+ urllib3.contrib: urllib3
+ urllib3.contrib.appengine: urllib3
+ urllib3.contrib.ntlmpool: urllib3
+ urllib3.contrib.pyopenssl: urllib3
+ urllib3.contrib.securetransport: urllib3
+ urllib3.contrib.socks: urllib3
+ urllib3.exceptions: urllib3
+ urllib3.fields: urllib3
+ urllib3.filepost: urllib3
+ urllib3.packages: urllib3
+ urllib3.packages.backports: urllib3
+ urllib3.packages.backports.makefile: urllib3
+ urllib3.packages.six: urllib3
+ urllib3.poolmanager: urllib3
+ urllib3.request: urllib3
+ urllib3.response: urllib3
+ urllib3.util: urllib3
+ urllib3.util.connection: urllib3
+ urllib3.util.proxy: urllib3
+ urllib3.util.queue: urllib3
+ urllib3.util.request: urllib3
+ urllib3.util.response: urllib3
+ urllib3.util.retry: urllib3
+ urllib3.util.ssl_: urllib3
+ urllib3.util.ssl_match_hostname: urllib3
+ urllib3.util.ssltransport: urllib3
+ urllib3.util.timeout: urllib3
+ urllib3.util.url: urllib3
+ urllib3.util.wait: urllib3
+ wrapt: wrapt
+ wrapt.arguments: wrapt
+ wrapt.decorators: wrapt
+ wrapt.importer: wrapt
+ wrapt.wrappers: wrapt
+ yaml: PyYAML
+ yaml.composer: PyYAML
+ yaml.constructor: PyYAML
+ yaml.cyaml: PyYAML
+ yaml.dumper: PyYAML
+ yaml.emitter: PyYAML
+ yaml.error: PyYAML
+ yaml.events: PyYAML
+ yaml.loader: PyYAML
+ yaml.nodes: PyYAML
+ yaml.parser: PyYAML
+ yaml.reader: PyYAML
+ yaml.representer: PyYAML
+ yaml.resolver: PyYAML
+ yaml.scanner: PyYAML
+ yaml.serializer: PyYAML
+ yaml.tokens: PyYAML
+ yamllint: yamllint
+ yamllint.cli: yamllint
+ yamllint.config: yamllint
+ yamllint.linter: yamllint
+ yamllint.parser: yamllint
+ yamllint.rules: yamllint
+ yamllint.rules.braces: yamllint
+ yamllint.rules.brackets: yamllint
+ yamllint.rules.colons: yamllint
+ yamllint.rules.commas: yamllint
+ yamllint.rules.comments: yamllint
+ yamllint.rules.comments_indentation: yamllint
+ yamllint.rules.common: yamllint
+ yamllint.rules.document_end: yamllint
+ yamllint.rules.document_start: yamllint
+ yamllint.rules.empty_lines: yamllint
+ yamllint.rules.empty_values: yamllint
+ yamllint.rules.float_values: yamllint
+ yamllint.rules.hyphens: yamllint
+ yamllint.rules.indentation: yamllint
+ yamllint.rules.key_duplicates: yamllint
+ yamllint.rules.key_ordering: yamllint
+ yamllint.rules.line_length: yamllint
+ yamllint.rules.new_line_at_end_of_file: yamllint
+ yamllint.rules.new_lines: yamllint
+ yamllint.rules.octal_values: yamllint
+ yamllint.rules.quoted_strings: yamllint
+ yamllint.rules.trailing_spaces: yamllint
+ yamllint.rules.truthy: yamllint
+ pip_repository:
+ name: pip
+ use_pip_repository_aliases: true
+integrity: d979738b10adbbaff0884837e4414688990491c6c40f6a25d58b9bb564411477
diff --git a/examples/bzlmod/lib.py b/examples/bzlmod/lib.py
new file mode 100644
index 0000000..646c6e8
--- /dev/null
+++ b/examples/bzlmod/lib.py
@@ -0,0 +1,19 @@
+# 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.
+
+from tabulate import tabulate
+
+
+def main(table):
+ return tabulate(table)
diff --git a/examples/bzlmod/libs/my_lib/BUILD.bazel b/examples/bzlmod/libs/my_lib/BUILD.bazel
new file mode 100644
index 0000000..2679d0e
--- /dev/null
+++ b/examples/bzlmod/libs/my_lib/BUILD.bazel
@@ -0,0 +1,9 @@
+load("@pip//:requirements.bzl", "requirement")
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "my_lib",
+ srcs = ["__init__.py"],
+ visibility = ["@//tests:__pkg__"],
+ deps = [requirement("websockets")],
+)
diff --git a/examples/bzlmod/libs/my_lib/__init__.py b/examples/bzlmod/libs/my_lib/__init__.py
new file mode 100644
index 0000000..6db2e85
--- /dev/null
+++ b/examples/bzlmod/libs/my_lib/__init__.py
@@ -0,0 +1,22 @@
+# 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.
+
+import websockets
+
+
+def websockets_is_for_python_version(sanitized_version_check):
+ # We are checking that the name of the repository folders
+ # match the expexted generated names. If we update the folder
+ # structure or naming we will need to modify this test
+ return f"pip_{sanitized_version_check}_websockets" in websockets.__file__
diff --git a/examples/bzlmod/other_module/MODULE.bazel b/examples/bzlmod/other_module/MODULE.bazel
new file mode 100644
index 0000000..cc23a51
--- /dev/null
+++ b/examples/bzlmod/other_module/MODULE.bazel
@@ -0,0 +1,34 @@
+module(
+ name = "other_module",
+)
+
+# This module is using the same version of rules_python
+# that the parent module uses.
+bazel_dep(name = "rules_python", version = "")
+
+# It is not best practice to use a python.toolchian in
+# a submodule. This code only exists to test that
+# we support doing this. This code is only for rules_python
+# testing purposes.
+PYTHON_NAME_39 = "python_3_9"
+
+PYTHON_NAME_311 = "python_3_11"
+
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
+python.toolchain(
+ configure_coverage_tool = True,
+ python_version = "3.9",
+)
+python.toolchain(
+ configure_coverage_tool = True,
+ # In a submodule this is ignored
+ is_default = True,
+ python_version = "3.11",
+)
+
+# created by the above python.toolchain calls.
+use_repo(
+ python,
+ PYTHON_NAME_39,
+ PYTHON_NAME_311,
+)
diff --git a/examples/bzlmod/other_module/WORKSPACE b/examples/bzlmod/other_module/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/bzlmod/other_module/WORKSPACE
diff --git a/examples/bzlmod/other_module/other_module/pkg/BUILD.bazel b/examples/bzlmod/other_module/other_module/pkg/BUILD.bazel
new file mode 100644
index 0000000..6e37df8
--- /dev/null
+++ b/examples/bzlmod/other_module/other_module/pkg/BUILD.bazel
@@ -0,0 +1,23 @@
+load("@python_3_11//:defs.bzl", py_binary_311 = "py_binary")
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "lib",
+ srcs = ["lib.py"],
+ data = ["data/data.txt"],
+ visibility = ["//visibility:public"],
+ deps = ["@rules_python//python/runfiles"],
+)
+
+# This is used for testing mulitple versions of Python. This is
+# used only when you need to support multiple versions of Python
+# in the same project.
+py_binary_311(
+ name = "lib_311",
+ srcs = ["lib.py"],
+ data = ["data/data.txt"],
+ visibility = ["//visibility:public"],
+ deps = ["@rules_python//python/runfiles"],
+)
+
+exports_files(["data/data.txt"])
diff --git a/examples/bzlmod/other_module/other_module/pkg/data/data.txt b/examples/bzlmod/other_module/other_module/pkg/data/data.txt
new file mode 100644
index 0000000..e975eaf
--- /dev/null
+++ b/examples/bzlmod/other_module/other_module/pkg/data/data.txt
@@ -0,0 +1 @@
+Hello, other_module!
diff --git a/examples/bzlmod/other_module/other_module/pkg/lib.py b/examples/bzlmod/other_module/other_module/pkg/lib.py
new file mode 100644
index 0000000..eaf65fb
--- /dev/null
+++ b/examples/bzlmod/other_module/other_module/pkg/lib.py
@@ -0,0 +1,27 @@
+# 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.
+
+from python.runfiles import runfiles
+
+
+def GetRunfilePathWithCurrentRepository():
+ r = runfiles.Create()
+ own_repo = r.CurrentRepository()
+ # For a non-main repository, the name of the runfiles directory is equal to
+ # the canonical repository name.
+ return r.Rlocation(own_repo + "/other_module/pkg/data/data.txt")
+
+
+def GetRunfilePathWithRepoMapping():
+ return runfiles.Create().Rlocation("other_module/other_module/pkg/data/data.txt")
diff --git a/examples/bzlmod/requirements.in b/examples/bzlmod/requirements.in
new file mode 100644
index 0000000..47cdcf1
--- /dev/null
+++ b/examples/bzlmod/requirements.in
@@ -0,0 +1,10 @@
+--extra-index-url https://pypi.python.org/simple/
+
+wheel
+websockets
+requests~=2.25.1
+s3cmd~=2.1.0
+yamllint>=1.28.0
+tabulate~=0.9.0
+pylint~=2.15.5
+python-dateutil>=2.8.2
diff --git a/examples/bzlmod/requirements_lock_3_10.txt b/examples/bzlmod/requirements_lock_3_10.txt
new file mode 100644
index 0000000..e3a185a
--- /dev/null
+++ b/examples/bzlmod/requirements_lock_3_10.txt
@@ -0,0 +1,327 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# bazel run //:requirements_3_10.update
+#
+--extra-index-url https://pypi.python.org/simple/
+
+astroid==2.13.5 \
+ --hash=sha256:6891f444625b6edb2ac798829b689e95297e100ddf89dbed5a8c610e34901501 \
+ --hash=sha256:df164d5ac811b9f44105a72b8f9d5edfb7b5b2d7e979b04ea377a77b3229114a
+ # via pylint
+certifi==2023.5.7 \
+ --hash=sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7 \
+ --hash=sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+dill==0.3.6 \
+ --hash=sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0 \
+ --hash=sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373
+ # via pylint
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+isort==5.12.0 \
+ --hash=sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504 \
+ --hash=sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6
+ # via pylint
+lazy-object-proxy==1.9.0 \
+ --hash=sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382 \
+ --hash=sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82 \
+ --hash=sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9 \
+ --hash=sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494 \
+ --hash=sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46 \
+ --hash=sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30 \
+ --hash=sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63 \
+ --hash=sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4 \
+ --hash=sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae \
+ --hash=sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be \
+ --hash=sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701 \
+ --hash=sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd \
+ --hash=sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006 \
+ --hash=sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a \
+ --hash=sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586 \
+ --hash=sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8 \
+ --hash=sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821 \
+ --hash=sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07 \
+ --hash=sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b \
+ --hash=sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171 \
+ --hash=sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b \
+ --hash=sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2 \
+ --hash=sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7 \
+ --hash=sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4 \
+ --hash=sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8 \
+ --hash=sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e \
+ --hash=sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f \
+ --hash=sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda \
+ --hash=sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4 \
+ --hash=sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e \
+ --hash=sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671 \
+ --hash=sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11 \
+ --hash=sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455 \
+ --hash=sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734 \
+ --hash=sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb \
+ --hash=sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59
+ # via astroid
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+pathspec==0.11.1 \
+ --hash=sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687 \
+ --hash=sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293
+ # via yamllint
+platformdirs==3.5.1 \
+ --hash=sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f \
+ --hash=sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5
+ # via pylint
+pylint==2.15.10 \
+ --hash=sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e \
+ --hash=sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5
+ # via -r requirements.in
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # -r requirements.in
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+tabulate==0.9.0 \
+ --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
+ --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via pylint
+tomlkit==0.11.8 \
+ --hash=sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171 \
+ --hash=sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3
+ # via pylint
+typing-extensions==4.6.3 \
+ --hash=sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26 \
+ --hash=sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5
+ # via astroid
+urllib3==1.26.16 \
+ --hash=sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f \
+ --hash=sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14
+ # via requests
+websockets==11.0.3 \
+ --hash=sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd \
+ --hash=sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f \
+ --hash=sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998 \
+ --hash=sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82 \
+ --hash=sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788 \
+ --hash=sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa \
+ --hash=sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f \
+ --hash=sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4 \
+ --hash=sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7 \
+ --hash=sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f \
+ --hash=sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd \
+ --hash=sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69 \
+ --hash=sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb \
+ --hash=sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b \
+ --hash=sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016 \
+ --hash=sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac \
+ --hash=sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4 \
+ --hash=sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb \
+ --hash=sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99 \
+ --hash=sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e \
+ --hash=sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54 \
+ --hash=sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf \
+ --hash=sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007 \
+ --hash=sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3 \
+ --hash=sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6 \
+ --hash=sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86 \
+ --hash=sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1 \
+ --hash=sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61 \
+ --hash=sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11 \
+ --hash=sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8 \
+ --hash=sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f \
+ --hash=sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931 \
+ --hash=sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526 \
+ --hash=sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016 \
+ --hash=sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae \
+ --hash=sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd \
+ --hash=sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b \
+ --hash=sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311 \
+ --hash=sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af \
+ --hash=sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152 \
+ --hash=sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288 \
+ --hash=sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de \
+ --hash=sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97 \
+ --hash=sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d \
+ --hash=sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d \
+ --hash=sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca \
+ --hash=sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0 \
+ --hash=sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9 \
+ --hash=sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b \
+ --hash=sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e \
+ --hash=sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128 \
+ --hash=sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d \
+ --hash=sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c \
+ --hash=sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5 \
+ --hash=sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6 \
+ --hash=sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b \
+ --hash=sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b \
+ --hash=sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280 \
+ --hash=sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c \
+ --hash=sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c \
+ --hash=sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f \
+ --hash=sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20 \
+ --hash=sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8 \
+ --hash=sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb \
+ --hash=sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602 \
+ --hash=sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf \
+ --hash=sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0 \
+ --hash=sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74 \
+ --hash=sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0 \
+ --hash=sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564
+ # via -r requirements.in
+wheel==0.40.0 \
+ --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \
+ --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247
+ # via -r requirements.in
+wrapt==1.15.0 \
+ --hash=sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0 \
+ --hash=sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420 \
+ --hash=sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a \
+ --hash=sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c \
+ --hash=sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079 \
+ --hash=sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923 \
+ --hash=sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f \
+ --hash=sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1 \
+ --hash=sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8 \
+ --hash=sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86 \
+ --hash=sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0 \
+ --hash=sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364 \
+ --hash=sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e \
+ --hash=sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c \
+ --hash=sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e \
+ --hash=sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c \
+ --hash=sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727 \
+ --hash=sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff \
+ --hash=sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e \
+ --hash=sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29 \
+ --hash=sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7 \
+ --hash=sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72 \
+ --hash=sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475 \
+ --hash=sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a \
+ --hash=sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317 \
+ --hash=sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2 \
+ --hash=sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd \
+ --hash=sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640 \
+ --hash=sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98 \
+ --hash=sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248 \
+ --hash=sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e \
+ --hash=sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d \
+ --hash=sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec \
+ --hash=sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1 \
+ --hash=sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e \
+ --hash=sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9 \
+ --hash=sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92 \
+ --hash=sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb \
+ --hash=sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094 \
+ --hash=sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46 \
+ --hash=sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29 \
+ --hash=sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd \
+ --hash=sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705 \
+ --hash=sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8 \
+ --hash=sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975 \
+ --hash=sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb \
+ --hash=sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e \
+ --hash=sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b \
+ --hash=sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418 \
+ --hash=sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019 \
+ --hash=sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1 \
+ --hash=sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba \
+ --hash=sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6 \
+ --hash=sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2 \
+ --hash=sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3 \
+ --hash=sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7 \
+ --hash=sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752 \
+ --hash=sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416 \
+ --hash=sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f \
+ --hash=sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1 \
+ --hash=sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc \
+ --hash=sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145 \
+ --hash=sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee \
+ --hash=sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a \
+ --hash=sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7 \
+ --hash=sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b \
+ --hash=sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653 \
+ --hash=sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0 \
+ --hash=sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90 \
+ --hash=sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29 \
+ --hash=sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6 \
+ --hash=sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034 \
+ --hash=sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09 \
+ --hash=sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559 \
+ --hash=sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639
+ # via astroid
+yamllint==1.32.0 \
+ --hash=sha256:d01dde008c65de5b235188ab3110bebc59d18e5c65fc8a58267cd211cd9df34a \
+ --hash=sha256:d97a66e48da820829d96077d76b8dfbe6c6140f106e558dae87e81ac4e6b30b7
+ # via -r requirements.in
diff --git a/examples/bzlmod/requirements_lock_3_9.txt b/examples/bzlmod/requirements_lock_3_9.txt
new file mode 100644
index 0000000..ba1d4d7
--- /dev/null
+++ b/examples/bzlmod/requirements_lock_3_9.txt
@@ -0,0 +1,305 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements_3_9.update
+#
+--extra-index-url https://pypi.python.org/simple/
+
+astroid==2.12.13 \
+ --hash=sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907 \
+ --hash=sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7
+ # via pylint
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+dill==0.3.6 \
+ --hash=sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0 \
+ --hash=sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373
+ # via pylint
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+isort==5.11.4 \
+ --hash=sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6 \
+ --hash=sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b
+ # via pylint
+lazy-object-proxy==1.8.0 \
+ --hash=sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada \
+ --hash=sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d \
+ --hash=sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7 \
+ --hash=sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe \
+ --hash=sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd \
+ --hash=sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c \
+ --hash=sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858 \
+ --hash=sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288 \
+ --hash=sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec \
+ --hash=sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f \
+ --hash=sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891 \
+ --hash=sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c \
+ --hash=sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25 \
+ --hash=sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156 \
+ --hash=sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8 \
+ --hash=sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f \
+ --hash=sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e \
+ --hash=sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0 \
+ --hash=sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b
+ # via astroid
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+pathspec==0.10.3 \
+ --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \
+ --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6
+ # via yamllint
+platformdirs==2.6.0 \
+ --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \
+ --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e
+ # via pylint
+pylint==2.15.9 \
+ --hash=sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4 \
+ --hash=sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb
+ # via -r requirements.in
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # -r requirements.in
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+tabulate==0.9.0 \
+ --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
+ --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via pylint
+tomlkit==0.11.6 \
+ --hash=sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b \
+ --hash=sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73
+ # via pylint
+typing-extensions==4.4.0 \
+ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
+ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
+ # via
+ # astroid
+ # pylint
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
+ # via requests
+websockets==11.0.3 \
+ --hash=sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd \
+ --hash=sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f \
+ --hash=sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998 \
+ --hash=sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82 \
+ --hash=sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788 \
+ --hash=sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa \
+ --hash=sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f \
+ --hash=sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4 \
+ --hash=sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7 \
+ --hash=sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f \
+ --hash=sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd \
+ --hash=sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69 \
+ --hash=sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb \
+ --hash=sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b \
+ --hash=sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016 \
+ --hash=sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac \
+ --hash=sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4 \
+ --hash=sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb \
+ --hash=sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99 \
+ --hash=sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e \
+ --hash=sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54 \
+ --hash=sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf \
+ --hash=sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007 \
+ --hash=sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3 \
+ --hash=sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6 \
+ --hash=sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86 \
+ --hash=sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1 \
+ --hash=sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61 \
+ --hash=sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11 \
+ --hash=sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8 \
+ --hash=sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f \
+ --hash=sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931 \
+ --hash=sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526 \
+ --hash=sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016 \
+ --hash=sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae \
+ --hash=sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd \
+ --hash=sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b \
+ --hash=sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311 \
+ --hash=sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af \
+ --hash=sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152 \
+ --hash=sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288 \
+ --hash=sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de \
+ --hash=sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97 \
+ --hash=sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d \
+ --hash=sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d \
+ --hash=sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca \
+ --hash=sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0 \
+ --hash=sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9 \
+ --hash=sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b \
+ --hash=sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e \
+ --hash=sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128 \
+ --hash=sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d \
+ --hash=sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c \
+ --hash=sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5 \
+ --hash=sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6 \
+ --hash=sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b \
+ --hash=sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b \
+ --hash=sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280 \
+ --hash=sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c \
+ --hash=sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c \
+ --hash=sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f \
+ --hash=sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20 \
+ --hash=sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8 \
+ --hash=sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb \
+ --hash=sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602 \
+ --hash=sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf \
+ --hash=sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0 \
+ --hash=sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74 \
+ --hash=sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0 \
+ --hash=sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564
+ # via -r requirements.in
+wheel==0.40.0 \
+ --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \
+ --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247
+ # via -r requirements.in
+wrapt==1.14.1 \
+ --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \
+ --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \
+ --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \
+ --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \
+ --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \
+ --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \
+ --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \
+ --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \
+ --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \
+ --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \
+ --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \
+ --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \
+ --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \
+ --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \
+ --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \
+ --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \
+ --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \
+ --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \
+ --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \
+ --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \
+ --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \
+ --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \
+ --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \
+ --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \
+ --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \
+ --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \
+ --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \
+ --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \
+ --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \
+ --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \
+ --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \
+ --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \
+ --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \
+ --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \
+ --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \
+ --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \
+ --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \
+ --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \
+ --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \
+ --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \
+ --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \
+ --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \
+ --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \
+ --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \
+ --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \
+ --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \
+ --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \
+ --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \
+ --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \
+ --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \
+ --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \
+ --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \
+ --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \
+ --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \
+ --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \
+ --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \
+ --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \
+ --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \
+ --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \
+ --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \
+ --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \
+ --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \
+ --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \
+ --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af
+ # via astroid
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
+ # via -r requirements.in
diff --git a/examples/bzlmod/requirements_windows_3_10.txt b/examples/bzlmod/requirements_windows_3_10.txt
new file mode 100644
index 0000000..9a28ae8
--- /dev/null
+++ b/examples/bzlmod/requirements_windows_3_10.txt
@@ -0,0 +1,331 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# bazel run //:requirements_3_10.update
+#
+--extra-index-url https://pypi.python.org/simple/
+
+astroid==2.13.5 \
+ --hash=sha256:6891f444625b6edb2ac798829b689e95297e100ddf89dbed5a8c610e34901501 \
+ --hash=sha256:df164d5ac811b9f44105a72b8f9d5edfb7b5b2d7e979b04ea377a77b3229114a
+ # via pylint
+certifi==2023.5.7 \
+ --hash=sha256:0f0d56dc5a6ad56fd4ba36484d6cc34451e1c6548c61daad8c320169f91eddc7 \
+ --hash=sha256:c6c2e98f5c7869efca1f8916fed228dd91539f9f1b444c314c06eef02980c716
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+colorama==0.4.6 \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+ # via pylint
+dill==0.3.6 \
+ --hash=sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0 \
+ --hash=sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373
+ # via pylint
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+isort==5.12.0 \
+ --hash=sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504 \
+ --hash=sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6
+ # via pylint
+lazy-object-proxy==1.9.0 \
+ --hash=sha256:09763491ce220c0299688940f8dc2c5d05fd1f45af1e42e636b2e8b2303e4382 \
+ --hash=sha256:0a891e4e41b54fd5b8313b96399f8b0e173bbbfc03c7631f01efbe29bb0bcf82 \
+ --hash=sha256:189bbd5d41ae7a498397287c408617fe5c48633e7755287b21d741f7db2706a9 \
+ --hash=sha256:18b78ec83edbbeb69efdc0e9c1cb41a3b1b1ed11ddd8ded602464c3fc6020494 \
+ --hash=sha256:1aa3de4088c89a1b69f8ec0dcc169aa725b0ff017899ac568fe44ddc1396df46 \
+ --hash=sha256:212774e4dfa851e74d393a2370871e174d7ff0ebc980907723bb67d25c8a7c30 \
+ --hash=sha256:2d0daa332786cf3bb49e10dc6a17a52f6a8f9601b4cf5c295a4f85854d61de63 \
+ --hash=sha256:5f83ac4d83ef0ab017683d715ed356e30dd48a93746309c8f3517e1287523ef4 \
+ --hash=sha256:659fb5809fa4629b8a1ac5106f669cfc7bef26fbb389dda53b3e010d1ac4ebae \
+ --hash=sha256:660c94ea760b3ce47d1855a30984c78327500493d396eac4dfd8bd82041b22be \
+ --hash=sha256:66a3de4a3ec06cd8af3f61b8e1ec67614fbb7c995d02fa224813cb7afefee701 \
+ --hash=sha256:721532711daa7db0d8b779b0bb0318fa87af1c10d7fe5e52ef30f8eff254d0cd \
+ --hash=sha256:7322c3d6f1766d4ef1e51a465f47955f1e8123caee67dd641e67d539a534d006 \
+ --hash=sha256:79a31b086e7e68b24b99b23d57723ef7e2c6d81ed21007b6281ebcd1688acb0a \
+ --hash=sha256:81fc4d08b062b535d95c9ea70dbe8a335c45c04029878e62d744bdced5141586 \
+ --hash=sha256:8fa02eaab317b1e9e03f69aab1f91e120e7899b392c4fc19807a8278a07a97e8 \
+ --hash=sha256:9090d8e53235aa280fc9239a86ae3ea8ac58eff66a705fa6aa2ec4968b95c821 \
+ --hash=sha256:946d27deaff6cf8452ed0dba83ba38839a87f4f7a9732e8f9fd4107b21e6ff07 \
+ --hash=sha256:9990d8e71b9f6488e91ad25f322898c136b008d87bf852ff65391b004da5e17b \
+ --hash=sha256:9cd077f3d04a58e83d04b20e334f678c2b0ff9879b9375ed107d5d07ff160171 \
+ --hash=sha256:9e7551208b2aded9c1447453ee366f1c4070602b3d932ace044715d89666899b \
+ --hash=sha256:9f5fa4a61ce2438267163891961cfd5e32ec97a2c444e5b842d574251ade27d2 \
+ --hash=sha256:b40387277b0ed2d0602b8293b94d7257e17d1479e257b4de114ea11a8cb7f2d7 \
+ --hash=sha256:bfb38f9ffb53b942f2b5954e0f610f1e721ccebe9cce9025a38c8ccf4a5183a4 \
+ --hash=sha256:cbf9b082426036e19c6924a9ce90c740a9861e2bdc27a4834fd0a910742ac1e8 \
+ --hash=sha256:d9e25ef10a39e8afe59a5c348a4dbf29b4868ab76269f81ce1674494e2565a6e \
+ --hash=sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f \
+ --hash=sha256:e7c21c95cae3c05c14aafffe2865bbd5e377cfc1348c4f7751d9dc9a48ca4bda \
+ --hash=sha256:e8c6cfb338b133fbdbc5cfaa10fe3c6aeea827db80c978dbd13bc9dd8526b7d4 \
+ --hash=sha256:ea806fd4c37bf7e7ad82537b0757999264d5f70c45468447bb2b91afdbe73a6e \
+ --hash=sha256:edd20c5a55acb67c7ed471fa2b5fb66cb17f61430b7a6b9c3b4a1e40293b1671 \
+ --hash=sha256:f0117049dd1d5635bbff65444496c90e0baa48ea405125c088e93d9cf4525b11 \
+ --hash=sha256:f0705c376533ed2a9e5e97aacdbfe04cecd71e0aa84c7c0595d02ef93b6e4455 \
+ --hash=sha256:f12ad7126ae0c98d601a7ee504c1122bcef553d1d5e0c3bfa77b16b3968d2734 \
+ --hash=sha256:f2457189d8257dd41ae9b434ba33298aec198e30adf2dcdaaa3a28b9994f6adb \
+ --hash=sha256:f699ac1c768270c9e384e4cbd268d6e67aebcfae6cd623b4d7c3bfde5a35db59
+ # via astroid
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+pathspec==0.11.1 \
+ --hash=sha256:2798de800fa92780e33acca925945e9a19a133b715067cf165b8866c15a31687 \
+ --hash=sha256:d8af70af76652554bd134c22b3e8a1cc46ed7d91edcdd721ef1a0c51a84a5293
+ # via yamllint
+platformdirs==3.5.1 \
+ --hash=sha256:412dae91f52a6f84830f39a8078cecd0e866cb72294a5c66808e74d5e88d251f \
+ --hash=sha256:e2378146f1964972c03c085bb5662ae80b2b8c06226c54b2ff4aa9483e8a13a5
+ # via pylint
+pylint==2.15.10 \
+ --hash=sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e \
+ --hash=sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5
+ # via -r requirements.in
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # -r requirements.in
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+tabulate==0.9.0 \
+ --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
+ --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via pylint
+tomlkit==0.11.8 \
+ --hash=sha256:8c726c4c202bdb148667835f68d68780b9a003a9ec34167b6c673b38eff2a171 \
+ --hash=sha256:9330fc7faa1db67b541b28e62018c17d20be733177d290a13b24c62d1614e0c3
+ # via pylint
+typing-extensions==4.6.3 \
+ --hash=sha256:88a4153d8505aabbb4e13aacb7c486c2b4a33ca3b3f807914a9b4c844c471c26 \
+ --hash=sha256:d91d5919357fe7f681a9f2b5b4cb2a5f1ef0a1e9f59c4d8ff0d3491e05c0ffd5
+ # via astroid
+urllib3==1.26.16 \
+ --hash=sha256:8d36afa7616d8ab714608411b4a3b13e58f463aee519024578e062e141dce20f \
+ --hash=sha256:8f135f6502756bde6b2a9b28989df5fbe87c9970cecaa69041edcce7f0589b14
+ # via requests
+websockets==11.0.3 \
+ --hash=sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd \
+ --hash=sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f \
+ --hash=sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998 \
+ --hash=sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82 \
+ --hash=sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788 \
+ --hash=sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa \
+ --hash=sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f \
+ --hash=sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4 \
+ --hash=sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7 \
+ --hash=sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f \
+ --hash=sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd \
+ --hash=sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69 \
+ --hash=sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb \
+ --hash=sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b \
+ --hash=sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016 \
+ --hash=sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac \
+ --hash=sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4 \
+ --hash=sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb \
+ --hash=sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99 \
+ --hash=sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e \
+ --hash=sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54 \
+ --hash=sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf \
+ --hash=sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007 \
+ --hash=sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3 \
+ --hash=sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6 \
+ --hash=sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86 \
+ --hash=sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1 \
+ --hash=sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61 \
+ --hash=sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11 \
+ --hash=sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8 \
+ --hash=sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f \
+ --hash=sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931 \
+ --hash=sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526 \
+ --hash=sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016 \
+ --hash=sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae \
+ --hash=sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd \
+ --hash=sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b \
+ --hash=sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311 \
+ --hash=sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af \
+ --hash=sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152 \
+ --hash=sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288 \
+ --hash=sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de \
+ --hash=sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97 \
+ --hash=sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d \
+ --hash=sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d \
+ --hash=sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca \
+ --hash=sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0 \
+ --hash=sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9 \
+ --hash=sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b \
+ --hash=sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e \
+ --hash=sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128 \
+ --hash=sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d \
+ --hash=sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c \
+ --hash=sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5 \
+ --hash=sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6 \
+ --hash=sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b \
+ --hash=sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b \
+ --hash=sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280 \
+ --hash=sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c \
+ --hash=sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c \
+ --hash=sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f \
+ --hash=sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20 \
+ --hash=sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8 \
+ --hash=sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb \
+ --hash=sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602 \
+ --hash=sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf \
+ --hash=sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0 \
+ --hash=sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74 \
+ --hash=sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0 \
+ --hash=sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564
+ # via -r requirements.in
+wheel==0.40.0 \
+ --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \
+ --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247
+ # via -r requirements.in
+wrapt==1.15.0 \
+ --hash=sha256:02fce1852f755f44f95af51f69d22e45080102e9d00258053b79367d07af39c0 \
+ --hash=sha256:077ff0d1f9d9e4ce6476c1a924a3332452c1406e59d90a2cf24aeb29eeac9420 \
+ --hash=sha256:078e2a1a86544e644a68422f881c48b84fef6d18f8c7a957ffd3f2e0a74a0d4a \
+ --hash=sha256:0970ddb69bba00670e58955f8019bec4a42d1785db3faa043c33d81de2bf843c \
+ --hash=sha256:1286eb30261894e4c70d124d44b7fd07825340869945c79d05bda53a40caa079 \
+ --hash=sha256:21f6d9a0d5b3a207cdf7acf8e58d7d13d463e639f0c7e01d82cdb671e6cb7923 \
+ --hash=sha256:230ae493696a371f1dbffaad3dafbb742a4d27a0afd2b1aecebe52b740167e7f \
+ --hash=sha256:26458da5653aa5b3d8dc8b24192f574a58984c749401f98fff994d41d3f08da1 \
+ --hash=sha256:2cf56d0e237280baed46f0b5316661da892565ff58309d4d2ed7dba763d984b8 \
+ --hash=sha256:2e51de54d4fb8fb50d6ee8327f9828306a959ae394d3e01a1ba8b2f937747d86 \
+ --hash=sha256:2fbfbca668dd15b744418265a9607baa970c347eefd0db6a518aaf0cfbd153c0 \
+ --hash=sha256:38adf7198f8f154502883242f9fe7333ab05a5b02de7d83aa2d88ea621f13364 \
+ --hash=sha256:3a8564f283394634a7a7054b7983e47dbf39c07712d7b177b37e03f2467a024e \
+ --hash=sha256:3abbe948c3cbde2689370a262a8d04e32ec2dd4f27103669a45c6929bcdbfe7c \
+ --hash=sha256:3bbe623731d03b186b3d6b0d6f51865bf598587c38d6f7b0be2e27414f7f214e \
+ --hash=sha256:40737a081d7497efea35ab9304b829b857f21558acfc7b3272f908d33b0d9d4c \
+ --hash=sha256:41d07d029dd4157ae27beab04d22b8e261eddfc6ecd64ff7000b10dc8b3a5727 \
+ --hash=sha256:46ed616d5fb42f98630ed70c3529541408166c22cdfd4540b88d5f21006b0eff \
+ --hash=sha256:493d389a2b63c88ad56cdc35d0fa5752daac56ca755805b1b0c530f785767d5e \
+ --hash=sha256:4ff0d20f2e670800d3ed2b220d40984162089a6e2c9646fdb09b85e6f9a8fc29 \
+ --hash=sha256:54accd4b8bc202966bafafd16e69da9d5640ff92389d33d28555c5fd4f25ccb7 \
+ --hash=sha256:56374914b132c702aa9aa9959c550004b8847148f95e1b824772d453ac204a72 \
+ --hash=sha256:578383d740457fa790fdf85e6d346fda1416a40549fe8db08e5e9bd281c6a475 \
+ --hash=sha256:58d7a75d731e8c63614222bcb21dd992b4ab01a399f1f09dd82af17bbfc2368a \
+ --hash=sha256:5c5aa28df055697d7c37d2099a7bc09f559d5053c3349b1ad0c39000e611d317 \
+ --hash=sha256:5fc8e02f5984a55d2c653f5fea93531e9836abbd84342c1d1e17abc4a15084c2 \
+ --hash=sha256:63424c681923b9f3bfbc5e3205aafe790904053d42ddcc08542181a30a7a51bd \
+ --hash=sha256:64b1df0f83706b4ef4cfb4fb0e4c2669100fd7ecacfb59e091fad300d4e04640 \
+ --hash=sha256:74934ebd71950e3db69960a7da29204f89624dde411afbfb3b4858c1409b1e98 \
+ --hash=sha256:75669d77bb2c071333417617a235324a1618dba66f82a750362eccbe5b61d248 \
+ --hash=sha256:75760a47c06b5974aa5e01949bf7e66d2af4d08cb8c1d6516af5e39595397f5e \
+ --hash=sha256:76407ab327158c510f44ded207e2f76b657303e17cb7a572ffe2f5a8a48aa04d \
+ --hash=sha256:76e9c727a874b4856d11a32fb0b389afc61ce8aaf281ada613713ddeadd1cfec \
+ --hash=sha256:77d4c1b881076c3ba173484dfa53d3582c1c8ff1f914c6461ab70c8428b796c1 \
+ --hash=sha256:780c82a41dc493b62fc5884fb1d3a3b81106642c5c5c78d6a0d4cbe96d62ba7e \
+ --hash=sha256:7dc0713bf81287a00516ef43137273b23ee414fe41a3c14be10dd95ed98a2df9 \
+ --hash=sha256:7eebcdbe3677e58dd4c0e03b4f2cfa346ed4049687d839adad68cc38bb559c92 \
+ --hash=sha256:896689fddba4f23ef7c718279e42f8834041a21342d95e56922e1c10c0cc7afb \
+ --hash=sha256:96177eb5645b1c6985f5c11d03fc2dbda9ad24ec0f3a46dcce91445747e15094 \
+ --hash=sha256:96e25c8603a155559231c19c0349245eeb4ac0096fe3c1d0be5c47e075bd4f46 \
+ --hash=sha256:9d37ac69edc5614b90516807de32d08cb8e7b12260a285ee330955604ed9dd29 \
+ --hash=sha256:9ed6aa0726b9b60911f4aed8ec5b8dd7bf3491476015819f56473ffaef8959bd \
+ --hash=sha256:a487f72a25904e2b4bbc0817ce7a8de94363bd7e79890510174da9d901c38705 \
+ --hash=sha256:a4cbb9ff5795cd66f0066bdf5947f170f5d63a9274f99bdbca02fd973adcf2a8 \
+ --hash=sha256:a74d56552ddbde46c246b5b89199cb3fd182f9c346c784e1a93e4dc3f5ec9975 \
+ --hash=sha256:a89ce3fd220ff144bd9d54da333ec0de0399b52c9ac3d2ce34b569cf1a5748fb \
+ --hash=sha256:abd52a09d03adf9c763d706df707c343293d5d106aea53483e0ec8d9e310ad5e \
+ --hash=sha256:abd8f36c99512755b8456047b7be10372fca271bf1467a1caa88db991e7c421b \
+ --hash=sha256:af5bd9ccb188f6a5fdda9f1f09d9f4c86cc8a539bd48a0bfdc97723970348418 \
+ --hash=sha256:b02f21c1e2074943312d03d243ac4388319f2456576b2c6023041c4d57cd7019 \
+ --hash=sha256:b06fa97478a5f478fb05e1980980a7cdf2712015493b44d0c87606c1513ed5b1 \
+ --hash=sha256:b0724f05c396b0a4c36a3226c31648385deb6a65d8992644c12a4963c70326ba \
+ --hash=sha256:b130fe77361d6771ecf5a219d8e0817d61b236b7d8b37cc045172e574ed219e6 \
+ --hash=sha256:b56d5519e470d3f2fe4aa7585f0632b060d532d0696c5bdfb5e8319e1d0f69a2 \
+ --hash=sha256:b67b819628e3b748fd3c2192c15fb951f549d0f47c0449af0764d7647302fda3 \
+ --hash=sha256:ba1711cda2d30634a7e452fc79eabcadaffedf241ff206db2ee93dd2c89a60e7 \
+ --hash=sha256:bbeccb1aa40ab88cd29e6c7d8585582c99548f55f9b2581dfc5ba68c59a85752 \
+ --hash=sha256:bd84395aab8e4d36263cd1b9308cd504f6cf713b7d6d3ce25ea55670baec5416 \
+ --hash=sha256:c99f4309f5145b93eca6e35ac1a988f0dc0a7ccf9ccdcd78d3c0adf57224e62f \
+ --hash=sha256:ca1cccf838cd28d5a0883b342474c630ac48cac5df0ee6eacc9c7290f76b11c1 \
+ --hash=sha256:cd525e0e52a5ff16653a3fc9e3dd827981917d34996600bbc34c05d048ca35cc \
+ --hash=sha256:cdb4f085756c96a3af04e6eca7f08b1345e94b53af8921b25c72f096e704e145 \
+ --hash=sha256:ce42618f67741d4697684e501ef02f29e758a123aa2d669e2d964ff734ee00ee \
+ --hash=sha256:d06730c6aed78cee4126234cf2d071e01b44b915e725a6cb439a879ec9754a3a \
+ --hash=sha256:d5fe3e099cf07d0fb5a1e23d399e5d4d1ca3e6dfcbe5c8570ccff3e9208274f7 \
+ --hash=sha256:d6bcbfc99f55655c3d93feb7ef3800bd5bbe963a755687cbf1f490a71fb7794b \
+ --hash=sha256:d787272ed958a05b2c86311d3a4135d3c2aeea4fc655705f074130aa57d71653 \
+ --hash=sha256:e169e957c33576f47e21864cf3fc9ff47c223a4ebca8960079b8bd36cb014fd0 \
+ --hash=sha256:e20076a211cd6f9b44a6be58f7eeafa7ab5720eb796975d0c03f05b47d89eb90 \
+ --hash=sha256:e826aadda3cae59295b95343db8f3d965fb31059da7de01ee8d1c40a60398b29 \
+ --hash=sha256:eef4d64c650f33347c1f9266fa5ae001440b232ad9b98f1f43dfe7a79435c0a6 \
+ --hash=sha256:f2e69b3ed24544b0d3dbe2c5c0ba5153ce50dcebb576fdc4696d52aa22db6034 \
+ --hash=sha256:f87ec75864c37c4c6cb908d282e1969e79763e0d9becdfe9fe5473b7bb1e5f09 \
+ --hash=sha256:fbec11614dba0424ca72f4e8ba3c420dba07b4a7c206c8c8e4e73f2e98f4c559 \
+ --hash=sha256:fd69666217b62fa5d7c6aa88e507493a34dec4fa20c5bd925e4bc12fce586639
+ # via astroid
+yamllint==1.32.0 \
+ --hash=sha256:d01dde008c65de5b235188ab3110bebc59d18e5c65fc8a58267cd211cd9df34a \
+ --hash=sha256:d97a66e48da820829d96077d76b8dfbe6c6140f106e558dae87e81ac4e6b30b7
+ # via -r requirements.in
diff --git a/examples/bzlmod/requirements_windows_3_9.txt b/examples/bzlmod/requirements_windows_3_9.txt
new file mode 100644
index 0000000..08f0979
--- /dev/null
+++ b/examples/bzlmod/requirements_windows_3_9.txt
@@ -0,0 +1,309 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements_3_9.update
+#
+--extra-index-url https://pypi.python.org/simple/
+
+astroid==2.12.13 \
+ --hash=sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907 \
+ --hash=sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7
+ # via pylint
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+colorama==0.4.6 \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+ # via pylint
+dill==0.3.6 \
+ --hash=sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0 \
+ --hash=sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373
+ # via pylint
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+isort==5.11.4 \
+ --hash=sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6 \
+ --hash=sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b
+ # via pylint
+lazy-object-proxy==1.8.0 \
+ --hash=sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada \
+ --hash=sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d \
+ --hash=sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7 \
+ --hash=sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe \
+ --hash=sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd \
+ --hash=sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c \
+ --hash=sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858 \
+ --hash=sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288 \
+ --hash=sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec \
+ --hash=sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f \
+ --hash=sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891 \
+ --hash=sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c \
+ --hash=sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25 \
+ --hash=sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156 \
+ --hash=sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8 \
+ --hash=sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f \
+ --hash=sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e \
+ --hash=sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0 \
+ --hash=sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b
+ # via astroid
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+pathspec==0.10.3 \
+ --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \
+ --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6
+ # via yamllint
+platformdirs==2.6.0 \
+ --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \
+ --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e
+ # via pylint
+pylint==2.15.9 \
+ --hash=sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4 \
+ --hash=sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb
+ # via -r requirements.in
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # -r requirements.in
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+tabulate==0.9.0 \
+ --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
+ --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via pylint
+tomlkit==0.11.6 \
+ --hash=sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b \
+ --hash=sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73
+ # via pylint
+typing-extensions==4.4.0 \
+ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
+ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
+ # via
+ # astroid
+ # pylint
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
+ # via requests
+websockets==11.0.3 \
+ --hash=sha256:01f5567d9cf6f502d655151645d4e8b72b453413d3819d2b6f1185abc23e82dd \
+ --hash=sha256:03aae4edc0b1c68498f41a6772d80ac7c1e33c06c6ffa2ac1c27a07653e79d6f \
+ --hash=sha256:0ac56b661e60edd453585f4bd68eb6a29ae25b5184fd5ba51e97652580458998 \
+ --hash=sha256:0ee68fe502f9031f19d495dae2c268830df2760c0524cbac5d759921ba8c8e82 \
+ --hash=sha256:1553cb82942b2a74dd9b15a018dce645d4e68674de2ca31ff13ebc2d9f283788 \
+ --hash=sha256:1a073fc9ab1c8aff37c99f11f1641e16da517770e31a37265d2755282a5d28aa \
+ --hash=sha256:1d2256283fa4b7f4c7d7d3e84dc2ece74d341bce57d5b9bf385df109c2a1a82f \
+ --hash=sha256:1d5023a4b6a5b183dc838808087033ec5df77580485fc533e7dab2567851b0a4 \
+ --hash=sha256:1fdf26fa8a6a592f8f9235285b8affa72748dc12e964a5518c6c5e8f916716f7 \
+ --hash=sha256:2529338a6ff0eb0b50c7be33dc3d0e456381157a31eefc561771ee431134a97f \
+ --hash=sha256:279e5de4671e79a9ac877427f4ac4ce93751b8823f276b681d04b2156713b9dd \
+ --hash=sha256:2d903ad4419f5b472de90cd2d40384573b25da71e33519a67797de17ef849b69 \
+ --hash=sha256:332d126167ddddec94597c2365537baf9ff62dfcc9db4266f263d455f2f031cb \
+ --hash=sha256:34fd59a4ac42dff6d4681d8843217137f6bc85ed29722f2f7222bd619d15e95b \
+ --hash=sha256:3580dd9c1ad0701169e4d6fc41e878ffe05e6bdcaf3c412f9d559389d0c9e016 \
+ --hash=sha256:3ccc8a0c387629aec40f2fc9fdcb4b9d5431954f934da3eaf16cdc94f67dbfac \
+ --hash=sha256:41f696ba95cd92dc047e46b41b26dd24518384749ed0d99bea0a941ca87404c4 \
+ --hash=sha256:42cc5452a54a8e46a032521d7365da775823e21bfba2895fb7b77633cce031bb \
+ --hash=sha256:4841ed00f1026dfbced6fca7d963c4e7043aa832648671b5138008dc5a8f6d99 \
+ --hash=sha256:4b253869ea05a5a073ebfdcb5cb3b0266a57c3764cf6fe114e4cd90f4bfa5f5e \
+ --hash=sha256:54c6e5b3d3a8936a4ab6870d46bdd6ec500ad62bde9e44462c32d18f1e9a8e54 \
+ --hash=sha256:619d9f06372b3a42bc29d0cd0354c9bb9fb39c2cbc1a9c5025b4538738dbffaf \
+ --hash=sha256:6505c1b31274723ccaf5f515c1824a4ad2f0d191cec942666b3d0f3aa4cb4007 \
+ --hash=sha256:660e2d9068d2bedc0912af508f30bbeb505bbbf9774d98def45f68278cea20d3 \
+ --hash=sha256:6681ba9e7f8f3b19440921e99efbb40fc89f26cd71bf539e45d8c8a25c976dc6 \
+ --hash=sha256:68b977f21ce443d6d378dbd5ca38621755f2063d6fdb3335bda981d552cfff86 \
+ --hash=sha256:69269f3a0b472e91125b503d3c0b3566bda26da0a3261c49f0027eb6075086d1 \
+ --hash=sha256:6f1a3f10f836fab6ca6efa97bb952300b20ae56b409414ca85bff2ad241d2a61 \
+ --hash=sha256:7622a89d696fc87af8e8d280d9b421db5133ef5b29d3f7a1ce9f1a7bf7fcfa11 \
+ --hash=sha256:777354ee16f02f643a4c7f2b3eff8027a33c9861edc691a2003531f5da4f6bc8 \
+ --hash=sha256:84d27a4832cc1a0ee07cdcf2b0629a8a72db73f4cf6de6f0904f6661227f256f \
+ --hash=sha256:8531fdcad636d82c517b26a448dcfe62f720e1922b33c81ce695d0edb91eb931 \
+ --hash=sha256:86d2a77fd490ae3ff6fae1c6ceaecad063d3cc2320b44377efdde79880e11526 \
+ --hash=sha256:88fc51d9a26b10fc331be344f1781224a375b78488fc343620184e95a4b27016 \
+ --hash=sha256:8a34e13a62a59c871064dfd8ffb150867e54291e46d4a7cf11d02c94a5275bae \
+ --hash=sha256:8c82f11964f010053e13daafdc7154ce7385ecc538989a354ccc7067fd7028fd \
+ --hash=sha256:92b2065d642bf8c0a82d59e59053dd2fdde64d4ed44efe4870fa816c1232647b \
+ --hash=sha256:97b52894d948d2f6ea480171a27122d77af14ced35f62e5c892ca2fae9344311 \
+ --hash=sha256:9d9acd80072abcc98bd2c86c3c9cd4ac2347b5a5a0cae7ed5c0ee5675f86d9af \
+ --hash=sha256:9f59a3c656fef341a99e3d63189852be7084c0e54b75734cde571182c087b152 \
+ --hash=sha256:aa5003845cdd21ac0dc6c9bf661c5beddd01116f6eb9eb3c8e272353d45b3288 \
+ --hash=sha256:b16fff62b45eccb9c7abb18e60e7e446998093cdcb50fed33134b9b6878836de \
+ --hash=sha256:b30c6590146e53149f04e85a6e4fcae068df4289e31e4aee1fdf56a0dead8f97 \
+ --hash=sha256:b58cbf0697721120866820b89f93659abc31c1e876bf20d0b3d03cef14faf84d \
+ --hash=sha256:b67c6f5e5a401fc56394f191f00f9b3811fe843ee93f4a70df3c389d1adf857d \
+ --hash=sha256:bceab846bac555aff6427d060f2fcfff71042dba6f5fca7dc4f75cac815e57ca \
+ --hash=sha256:bee9fcb41db2a23bed96c6b6ead6489702c12334ea20a297aa095ce6d31370d0 \
+ --hash=sha256:c114e8da9b475739dde229fd3bc6b05a6537a88a578358bc8eb29b4030fac9c9 \
+ --hash=sha256:c1f0524f203e3bd35149f12157438f406eff2e4fb30f71221c8a5eceb3617b6b \
+ --hash=sha256:c792ea4eabc0159535608fc5658a74d1a81020eb35195dd63214dcf07556f67e \
+ --hash=sha256:c7f3cb904cce8e1be667c7e6fef4516b98d1a6a0635a58a57528d577ac18a128 \
+ --hash=sha256:d67ac60a307f760c6e65dad586f556dde58e683fab03323221a4e530ead6f74d \
+ --hash=sha256:dcacf2c7a6c3a84e720d1bb2b543c675bf6c40e460300b628bab1b1efc7c034c \
+ --hash=sha256:de36fe9c02995c7e6ae6efe2e205816f5f00c22fd1fbf343d4d18c3d5ceac2f5 \
+ --hash=sha256:def07915168ac8f7853812cc593c71185a16216e9e4fa886358a17ed0fd9fcf6 \
+ --hash=sha256:df41b9bc27c2c25b486bae7cf42fccdc52ff181c8c387bfd026624a491c2671b \
+ --hash=sha256:e052b8467dd07d4943936009f46ae5ce7b908ddcac3fda581656b1b19c083d9b \
+ --hash=sha256:e063b1865974611313a3849d43f2c3f5368093691349cf3c7c8f8f75ad7cb280 \
+ --hash=sha256:e1459677e5d12be8bbc7584c35b992eea142911a6236a3278b9b5ce3326f282c \
+ --hash=sha256:e1a99a7a71631f0efe727c10edfba09ea6bee4166a6f9c19aafb6c0b5917d09c \
+ --hash=sha256:e590228200fcfc7e9109509e4d9125eace2042fd52b595dd22bbc34bb282307f \
+ --hash=sha256:e6316827e3e79b7b8e7d8e3b08f4e331af91a48e794d5d8b099928b6f0b85f20 \
+ --hash=sha256:e7837cb169eca3b3ae94cc5787c4fed99eef74c0ab9506756eea335e0d6f3ed8 \
+ --hash=sha256:e848f46a58b9fcf3d06061d17be388caf70ea5b8cc3466251963c8345e13f7eb \
+ --hash=sha256:ed058398f55163a79bb9f06a90ef9ccc063b204bb346c4de78efc5d15abfe602 \
+ --hash=sha256:f2e58f2c36cc52d41f2659e4c0cbf7353e28c8c9e63e30d8c6d3494dc9fdedcf \
+ --hash=sha256:f467ba0050b7de85016b43f5a22b46383ef004c4f672148a8abf32bc999a87f0 \
+ --hash=sha256:f61bdb1df43dc9c131791fbc2355535f9024b9a04398d3bd0684fc16ab07df74 \
+ --hash=sha256:fb06eea71a00a7af0ae6aefbb932fb8a7df3cb390cc217d51a9ad7343de1b8d0 \
+ --hash=sha256:ffd7dcaf744f25f82190856bc26ed81721508fc5cbf2a330751e135ff1283564
+ # via -r requirements.in
+wheel==0.40.0 \
+ --hash=sha256:cd1196f3faee2b31968d626e1731c94f99cbdb67cf5a46e4f5656cbee7738873 \
+ --hash=sha256:d236b20e7cb522daf2390fa84c55eea81c5c30190f90f29ae2ca1ad8355bf247
+ # via -r requirements.in
+wrapt==1.14.1 \
+ --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \
+ --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \
+ --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \
+ --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \
+ --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \
+ --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \
+ --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \
+ --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \
+ --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \
+ --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \
+ --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \
+ --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \
+ --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \
+ --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \
+ --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \
+ --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \
+ --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \
+ --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \
+ --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \
+ --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \
+ --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \
+ --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \
+ --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \
+ --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \
+ --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \
+ --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \
+ --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \
+ --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \
+ --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \
+ --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \
+ --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \
+ --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \
+ --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \
+ --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \
+ --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \
+ --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \
+ --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \
+ --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \
+ --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \
+ --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \
+ --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \
+ --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \
+ --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \
+ --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \
+ --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \
+ --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \
+ --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \
+ --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \
+ --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \
+ --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \
+ --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \
+ --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \
+ --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \
+ --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \
+ --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \
+ --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \
+ --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \
+ --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \
+ --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \
+ --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \
+ --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \
+ --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \
+ --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \
+ --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af
+ # via astroid
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
+ # via -r requirements.in
diff --git a/examples/bzlmod/runfiles/BUILD.bazel b/examples/bzlmod/runfiles/BUILD.bazel
new file mode 100644
index 0000000..add56b3
--- /dev/null
+++ b/examples/bzlmod/runfiles/BUILD.bazel
@@ -0,0 +1,18 @@
+load("@rules_python//python:defs.bzl", "py_test")
+
+py_test(
+ name = "runfiles_test",
+ srcs = ["runfiles_test.py"],
+ data = [
+ "data/data.txt",
+ "@our_other_module//other_module/pkg:data/data.txt",
+ ],
+ env = {
+ "DATA_RLOCATIONPATH": "$(rlocationpath data/data.txt)",
+ "OTHER_MODULE_DATA_RLOCATIONPATH": "$(rlocationpath @our_other_module//other_module/pkg:data/data.txt)",
+ },
+ deps = [
+ "@our_other_module//other_module/pkg:lib",
+ "@rules_python//python/runfiles",
+ ],
+)
diff --git a/examples/bzlmod/runfiles/data/data.txt b/examples/bzlmod/runfiles/data/data.txt
new file mode 100644
index 0000000..fb17e0d
--- /dev/null
+++ b/examples/bzlmod/runfiles/data/data.txt
@@ -0,0 +1 @@
+Hello, example_bzlmod!
diff --git a/examples/bzlmod/runfiles/runfiles_test.py b/examples/bzlmod/runfiles/runfiles_test.py
new file mode 100644
index 0000000..e1ba14e
--- /dev/null
+++ b/examples/bzlmod/runfiles/runfiles_test.py
@@ -0,0 +1,64 @@
+# 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.
+
+import os
+import unittest
+
+from other_module.pkg import lib
+
+from python.runfiles import runfiles
+
+
+class RunfilesTest(unittest.TestCase):
+ # """Unit tests for `runfiles.Runfiles`."""
+ def testCurrentRepository(self):
+ self.assertEqual(runfiles.Create().CurrentRepository(), "")
+
+ def testRunfilesWithRepoMapping(self):
+ data_path = runfiles.Create().Rlocation("example_bzlmod/runfiles/data/data.txt")
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, example_bzlmod!")
+
+ def testRunfileWithRlocationpath(self):
+ data_rlocationpath = os.getenv("DATA_RLOCATIONPATH")
+ data_path = runfiles.Create().Rlocation(data_rlocationpath)
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, example_bzlmod!")
+
+ def testRunfileInOtherModuleWithOurRepoMapping(self):
+ data_path = runfiles.Create().Rlocation(
+ "our_other_module/other_module/pkg/data/data.txt"
+ )
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+ def testRunfileInOtherModuleWithItsRepoMapping(self):
+ data_path = lib.GetRunfilePathWithRepoMapping()
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+ def testRunfileInOtherModuleWithCurrentRepository(self):
+ data_path = lib.GetRunfilePathWithCurrentRepository()
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+ def testRunfileInOtherModuleWithRlocationpath(self):
+ data_rlocationpath = os.getenv("OTHER_MODULE_DATA_RLOCATIONPATH")
+ data_path = runfiles.Create().Rlocation(data_rlocationpath)
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/bzlmod/test.py b/examples/bzlmod/test.py
new file mode 100644
index 0000000..80cd027
--- /dev/null
+++ b/examples/bzlmod/test.py
@@ -0,0 +1,90 @@
+# 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.
+
+import os
+import pathlib
+import sys
+import unittest
+
+from lib import main
+
+
+class ExampleTest(unittest.TestCase):
+ def test_coverage_doesnt_shadow_stdlib(self):
+ # When we try to import the html module
+ import html as html_stdlib
+
+ try:
+ import coverage.html as html_coverage
+ except ImportError:
+ self.skipTest("not running under coverage, skipping")
+
+ self.assertEqual(
+ "html",
+ f"{html_stdlib.__name__}",
+ "'html' from stdlib was not loaded correctly",
+ )
+
+ self.assertEqual(
+ "coverage.html",
+ f"{html_coverage.__name__}",
+ "'coverage.html' was not loaded correctly",
+ )
+
+ self.assertNotEqual(
+ html_stdlib,
+ html_coverage,
+ "'html' import should not be shadowed by coverage",
+ )
+
+ def test_coverage_sys_path(self):
+ all_paths = ",\n ".join(sys.path)
+
+ for i, path in enumerate(sys.path[1:-2]):
+ self.assertFalse(
+ "/coverage" in path,
+ f"Expected {i + 2}th '{path}' to not contain 'coverage.py' paths, "
+ f"sys.path has {len(sys.path)} items:\n {all_paths}",
+ )
+
+ first_item, last_item = sys.path[0], sys.path[-1]
+ self.assertFalse(
+ first_item.endswith("coverage"),
+ f"Expected the first item in sys.path '{first_item}' to not be related to coverage",
+ )
+ if os.environ.get("COVERAGE_MANIFEST"):
+ # we are running under the 'bazel coverage :test'
+ self.assertTrue(
+ "_coverage" in last_item,
+ f"Expected {last_item} to be related to coverage",
+ )
+ self.assertEqual(pathlib.Path(last_item).name, "coverage")
+ else:
+ self.assertFalse(
+ "coverage" in last_item, f"Expected coverage tooling to not be present"
+ )
+
+ def test_main(self):
+ self.assertEquals(
+ """\
+- -
+A 1
+B 2
+- -""",
+ main([["A", 1], ["B", 2]]),
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/bzlmod/tests/BUILD.bazel b/examples/bzlmod/tests/BUILD.bazel
new file mode 100644
index 0000000..ce7079c
--- /dev/null
+++ b/examples/bzlmod/tests/BUILD.bazel
@@ -0,0 +1,135 @@
+load("@python_versions//3.10:defs.bzl", py_binary_3_10 = "py_binary", py_test_3_10 = "py_test")
+load("@python_versions//3.11:defs.bzl", py_binary_3_11 = "py_binary", py_test_3_11 = "py_test")
+load("@python_versions//3.9:defs.bzl", py_binary_3_9 = "py_binary", py_test_3_9 = "py_test")
+load("@rules_python//python:defs.bzl", "py_binary", "py_test")
+
+py_binary(
+ name = "version_default",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+py_binary_3_9(
+ name = "version_3_9",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+py_binary_3_10(
+ name = "version_3_10",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+py_binary_3_11(
+ name = "version_3_11",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+# This is a work in progress and the commented
+# tests will not work until we can support
+# multiple pips with bzlmod.
+
+py_test(
+ name = "my_lib_default_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+py_test_3_9(
+ name = "my_lib_3_9_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+py_test_3_10(
+ name = "my_lib_3_10_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+py_test(
+ name = "version_default_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.9"}, # The default defined in the WORKSPACE.
+ main = "version_test.py",
+)
+
+py_test_3_9(
+ name = "version_3_9_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.9"},
+ main = "version_test.py",
+)
+
+py_test_3_10(
+ name = "version_3_10_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.10"},
+ main = "version_test.py",
+)
+
+py_test_3_11(
+ name = "version_3_11_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.11"},
+ main = "version_test.py",
+)
+
+py_test(
+ name = "version_default_takes_3_10_subprocess_test",
+ srcs = ["cross_version_test.py"],
+ data = [":version_3_10"],
+ env = {
+ "SUBPROCESS_VERSION_CHECK": "3.10",
+ "SUBPROCESS_VERSION_PY_BINARY": "$(rootpath :version_3_10)",
+ "VERSION_CHECK": "3.9",
+ },
+ main = "cross_version_test.py",
+)
+
+py_test_3_10(
+ name = "version_3_10_takes_3_9_subprocess_test",
+ srcs = ["cross_version_test.py"],
+ data = [":version_3_9"],
+ env = {
+ "SUBPROCESS_VERSION_CHECK": "3.9",
+ "SUBPROCESS_VERSION_PY_BINARY": "$(rootpath :version_3_9)",
+ "VERSION_CHECK": "3.10",
+ },
+ main = "cross_version_test.py",
+)
+
+sh_test(
+ name = "version_test_binary_default",
+ srcs = ["version_test.sh"],
+ data = [":version_default"],
+ env = {
+ "VERSION_CHECK": "3.9", # The default defined in the WORKSPACE.
+ "VERSION_PY_BINARY": "$(rootpath :version_default)",
+ },
+)
+
+sh_test(
+ name = "version_test_binary_3_9",
+ srcs = ["version_test.sh"],
+ data = [":version_3_9"],
+ env = {
+ "VERSION_CHECK": "3.9",
+ "VERSION_PY_BINARY": "$(rootpath :version_3_9)",
+ },
+)
+
+sh_test(
+ name = "version_test_binary_3_10",
+ srcs = ["version_test.sh"],
+ data = [":version_3_10"],
+ env = {
+ "VERSION_CHECK": "3.10",
+ "VERSION_PY_BINARY": "$(rootpath :version_3_10)",
+ },
+)
diff --git a/examples/bzlmod/tests/cross_version_test.py b/examples/bzlmod/tests/cross_version_test.py
new file mode 100644
index 0000000..437be2e
--- /dev/null
+++ b/examples/bzlmod/tests/cross_version_test.py
@@ -0,0 +1,39 @@
+# 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.
+
+import os
+import subprocess
+import sys
+
+process = subprocess.run(
+ [os.getenv("SUBPROCESS_VERSION_PY_BINARY")],
+ stdout=subprocess.PIPE,
+ universal_newlines=True,
+)
+
+subprocess_current = process.stdout.strip()
+subprocess_expected = os.getenv("SUBPROCESS_VERSION_CHECK")
+
+if subprocess_current != subprocess_expected:
+ print(
+ f"expected subprocess version '{subprocess_expected}' is different than returned '{subprocess_current}'"
+ )
+ sys.exit(1)
+
+expected = os.getenv("VERSION_CHECK")
+current = f"{sys.version_info.major}.{sys.version_info.minor}"
+
+if current != expected:
+ print(f"expected version '{expected}' is different than returned '{current}'")
+ sys.exit(1)
diff --git a/examples/bzlmod/tests/my_lib_test.py b/examples/bzlmod/tests/my_lib_test.py
new file mode 100644
index 0000000..b06374c
--- /dev/null
+++ b/examples/bzlmod/tests/my_lib_test.py
@@ -0,0 +1,26 @@
+# 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.
+
+import os
+import sys
+
+import libs.my_lib as my_lib
+
+# This variable is used to match the repository folder structure
+# If we update the folder structure or naming we need to modify this test.
+sanitized_version_check = f"{sys.version_info.major}{sys.version_info.minor}"
+
+if not my_lib.websockets_is_for_python_version(sanitized_version_check):
+ print("expected package for Python version is different than returned")
+ sys.exit(1)
diff --git a/examples/bzlmod/tests/version.py b/examples/bzlmod/tests/version.py
new file mode 100644
index 0000000..2d293c1
--- /dev/null
+++ b/examples/bzlmod/tests/version.py
@@ -0,0 +1,17 @@
+# 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.
+
+import sys
+
+print(f"{sys.version_info.major}.{sys.version_info.minor}")
diff --git a/examples/bzlmod/tests/version_test.py b/examples/bzlmod/tests/version_test.py
new file mode 100644
index 0000000..444f5e4
--- /dev/null
+++ b/examples/bzlmod/tests/version_test.py
@@ -0,0 +1,23 @@
+# 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.
+
+import os
+import sys
+
+expected = os.getenv("VERSION_CHECK")
+current = f"{sys.version_info.major}.{sys.version_info.minor}"
+
+if current != expected:
+ print(f"expected version '{expected}' is different than returned '{current}'")
+ sys.exit(1)
diff --git a/examples/bzlmod/tests/version_test.sh b/examples/bzlmod/tests/version_test.sh
new file mode 100755
index 0000000..3bedb95
--- /dev/null
+++ b/examples/bzlmod/tests/version_test.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# 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.
+
+
+set -o errexit -o nounset -o pipefail
+
+version_py_binary=$("${VERSION_PY_BINARY}")
+
+if [[ "${version_py_binary}" != "${VERSION_CHECK}" ]]; then
+ echo >&2 "expected version '${VERSION_CHECK}' is different than returned '${version_py_binary}'"
+ exit 1
+fi
diff --git a/examples/bzlmod/whl_mods/BUILD.bazel b/examples/bzlmod/whl_mods/BUILD.bazel
new file mode 100644
index 0000000..6ca07dd
--- /dev/null
+++ b/examples/bzlmod/whl_mods/BUILD.bazel
@@ -0,0 +1,21 @@
+load("@rules_python//python:defs.bzl", "py_test")
+
+exports_files(
+ glob(["data/**"]),
+ visibility = ["//visibility:public"],
+)
+
+py_test(
+ name = "pip_whl_mods_test",
+ srcs = ["pip_whl_mods_test.py"],
+ env = {
+ "REQUESTS_PKG_DIR": "pip_39_requests",
+ "WHEEL_PKG_DIR": "pip_39_wheel",
+ },
+ main = "pip_whl_mods_test.py",
+ deps = [
+ "@pip//requests:pkg",
+ "@pip//wheel:pkg",
+ "@rules_python//python/runfiles",
+ ],
+)
diff --git a/examples/bzlmod/whl_mods/appended_build_content.BUILD b/examples/bzlmod/whl_mods/appended_build_content.BUILD
new file mode 100644
index 0000000..7a9f3a2
--- /dev/null
+++ b/examples/bzlmod/whl_mods/appended_build_content.BUILD
@@ -0,0 +1,7 @@
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+
+write_file(
+ name = "generated_file",
+ out = "generated_file.txt",
+ content = ["Hello world from requests"],
+)
diff --git a/examples/bzlmod/whl_mods/data/copy_executable.py b/examples/bzlmod/whl_mods/data/copy_executable.py
new file mode 100755
index 0000000..5cb1af7
--- /dev/null
+++ b/examples/bzlmod/whl_mods/data/copy_executable.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# 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.
+
+
+if __name__ == "__main__":
+ print("Hello world from copied executable")
diff --git a/examples/bzlmod/whl_mods/data/copy_file.txt b/examples/bzlmod/whl_mods/data/copy_file.txt
new file mode 100644
index 0000000..b1020f7
--- /dev/null
+++ b/examples/bzlmod/whl_mods/data/copy_file.txt
@@ -0,0 +1 @@
+Hello world from copied file
diff --git a/examples/bzlmod/whl_mods/pip_whl_mods_test.py b/examples/bzlmod/whl_mods/pip_whl_mods_test.py
new file mode 100644
index 0000000..c739b80
--- /dev/null
+++ b/examples/bzlmod/whl_mods/pip_whl_mods_test.py
@@ -0,0 +1,130 @@
+#!/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.
+
+
+import os
+import platform
+import subprocess
+import sys
+import unittest
+from pathlib import Path
+
+from python.runfiles import runfiles
+
+
+class PipWhlModsTest(unittest.TestCase):
+ maxDiff = None
+
+ def package_path(self) -> str:
+ return "rules_python~override~pip~"
+
+ def wheel_pkg_dir(self) -> str:
+ env = os.environ.get("WHEEL_PKG_DIR")
+ self.assertIsNotNone(env)
+ return env
+
+ def test_build_content_and_data(self):
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "{}{}/generated_file.txt".format(
+ self.package_path(),
+ self.wheel_pkg_dir(),
+ ),
+ )
+ generated_file = Path(rpath)
+ self.assertTrue(generated_file.exists())
+
+ content = generated_file.read_text().rstrip()
+ self.assertEqual(content, "Hello world from build content file")
+
+ def test_copy_files(self):
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "{}{}/copied_content/file.txt".format(
+ self.package_path(),
+ self.wheel_pkg_dir(),
+ )
+ )
+ copied_file = Path(rpath)
+ self.assertTrue(copied_file.exists())
+
+ content = copied_file.read_text().rstrip()
+ self.assertEqual(content, "Hello world from copied file")
+
+ def test_copy_executables(self):
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "{}{}/copied_content/executable{}".format(
+ self.package_path(),
+ self.wheel_pkg_dir(),
+ ".exe" if platform.system() == "windows" else ".py",
+ )
+ )
+ executable = Path(rpath)
+ self.assertTrue(executable.exists())
+
+ proc = subprocess.run(
+ [sys.executable, str(executable)],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ stdout = proc.stdout.decode("utf-8").strip()
+ self.assertEqual(stdout, "Hello world from copied executable")
+
+ def test_data_exclude_glob(self):
+ current_wheel_version = "0.40.0"
+
+ r = runfiles.Create()
+ dist_info_dir = "{}{}/site-packages/wheel-{}.dist-info".format(
+ self.package_path(),
+ self.wheel_pkg_dir(),
+ current_wheel_version,
+ )
+
+ # Note: `METADATA` is important as it's consumed by https://docs.python.org/3/library/importlib.metadata.html
+ # `METADATA` is expected to be there to show dist-info files are included in the runfiles.
+ metadata_path = r.Rlocation("{}/METADATA".format(dist_info_dir))
+
+ # However, `WHEEL` was explicitly excluded, so it should be missing
+ wheel_path = r.Rlocation("{}/WHEEL".format(dist_info_dir))
+
+ self.assertTrue(Path(metadata_path).exists())
+ self.assertFalse(Path(wheel_path).exists())
+
+ def requests_pkg_dir(self) -> str:
+ env = os.environ.get("REQUESTS_PKG_DIR")
+ self.assertIsNotNone(env)
+ return env
+
+ def test_extra(self):
+ # This test verifies that annotations work correctly for pip packages with extras
+ # specified, in this case requests[security].
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "{}{}/generated_file.txt".format(
+ self.package_path(),
+ self.requests_pkg_dir(),
+ ),
+ )
+ generated_file = Path(rpath)
+ self.assertTrue(generated_file.exists())
+
+ content = generated_file.read_text().rstrip()
+ self.assertEqual(content, "Hello world from requests")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/bzlmod_build_file_generation/.bazelignore b/examples/bzlmod_build_file_generation/.bazelignore
new file mode 100644
index 0000000..ab3eb16
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/.bazelignore
@@ -0,0 +1 @@
+other_module
diff --git a/examples/bzlmod_build_file_generation/.bazelrc b/examples/bzlmod_build_file_generation/.bazelrc
new file mode 100644
index 0000000..1fbada7
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/.bazelrc
@@ -0,0 +1,9 @@
+test --test_output=errors --enable_runfiles
+
+# Windows requires these for multi-python support:
+build --enable_runfiles
+startup --windows_enable_symlinks
+
+common --experimental_enable_bzlmod
+
+coverage --java_runtime_version=remotejdk_11
diff --git a/examples/bzlmod_build_file_generation/.bazelversion b/examples/bzlmod_build_file_generation/.bazelversion
new file mode 100644
index 0000000..09b254e
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/.bazelversion
@@ -0,0 +1 @@
+6.0.0
diff --git a/examples/bzlmod_build_file_generation/.gitignore b/examples/bzlmod_build_file_generation/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/examples/bzlmod_build_file_generation/BUILD.bazel b/examples/bzlmod_build_file_generation/BUILD.bazel
new file mode 100644
index 0000000..c5e27c2
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/BUILD.bazel
@@ -0,0 +1,98 @@
+# Load various rules so that we can have bazel download
+# various rulesets and dependencies.
+# The `load` statement imports the symbol for the rule, in the defined
+# ruleset. When the symbol is loaded you can use the rule.
+
+# The following code loads the base python requirements and gazelle
+# requirements.
+load("@bazel_gazelle//:def.bzl", "gazelle")
+load("@pip//:requirements.bzl", "all_whl_requirements")
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+load("@rules_python_gazelle_plugin//:def.bzl", "GAZELLE_PYTHON_RUNTIME_DEPS")
+load("@rules_python_gazelle_plugin//manifest:defs.bzl", "gazelle_python_manifest")
+load("@rules_python_gazelle_plugin//modules_mapping:def.bzl", "modules_mapping")
+
+# This stanza calls a rule that generates targets for managing pip dependencies
+# with pip-compile.
+compile_pip_requirements(
+ name = "requirements",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock.txt",
+ requirements_windows = "requirements_windows.txt",
+)
+
+# This repository rule fetches the metadata for python packages we
+# depend on. That data is required for the gazelle_python_manifest
+# rule to update our manifest file.
+modules_mapping(
+ name = "modules_map",
+ exclude_patterns = [
+ "^_|(\\._)+", # This is the default.
+ "(\\.tests)+", # Add a custom one to get rid of the psutil tests.
+ ],
+ wheels = all_whl_requirements,
+)
+
+# Gazelle python extension needs a manifest file mapping from
+# an import to the installed package that provides it.
+# This macro produces two targets:
+# - //:gazelle_python_manifest.update can be used with `bazel run`
+# to recalculate the manifest
+# - //:gazelle_python_manifest.test is a test target ensuring that
+# the manifest doesn't need to be updated
+# This target updates a file called gazelle_python.yaml, and
+# requires that file exist before the target is run.
+# When you are using gazelle you need to run this target first.
+gazelle_python_manifest(
+ name = "gazelle_python_manifest",
+ modules_mapping = ":modules_map",
+ pip_repository_name = "pip",
+ requirements = [
+ "//:requirements_lock.txt",
+ "//:requirements_windows.txt",
+ ],
+ # NOTE: we can use this flag in order to make our setup compatible with
+ # bzlmod.
+ use_pip_repository_aliases = True,
+)
+
+# Our gazelle target points to the python gazelle binary.
+# This is the simple case where we only need one language supported.
+# If you also had proto, go, or other gazelle-supported languages,
+# you would also need a gazelle_binary rule.
+# See https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.rst#example
+# This is the primary gazelle target to run, so that you can update BUILD.bazel files.
+# You can execute:
+# - bazel run //:gazelle update
+# - bazel run //:gazelle fix
+# See: https://github.com/bazelbuild/bazel-gazelle#fix-and-update
+gazelle(
+ name = "gazelle",
+ data = GAZELLE_PYTHON_RUNTIME_DEPS,
+ gazelle = "@rules_python_gazelle_plugin//python:gazelle_binary",
+)
+
+# The following targets are created and maintained by gazelle
+py_library(
+ name = "bzlmod_build_file_generation",
+ srcs = ["lib.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@pip//tabulate"],
+)
+
+py_binary(
+ name = "bzlmod_build_file_generation_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [":bzlmod_build_file_generation"],
+)
+
+py_test(
+ name = "bzlmod_build_file_generation_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":bzlmod_build_file_generation"],
+)
diff --git a/examples/bzlmod_build_file_generation/MODULE.bazel b/examples/bzlmod_build_file_generation/MODULE.bazel
new file mode 100644
index 0000000..6bc5880
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/MODULE.bazel
@@ -0,0 +1,87 @@
+# This file replaces the WORKSPACE file when using bzlmod.
+
+# module declares certain properties of the Bazel module represented by the current Bazel repo.
+# These properties are either essential metadata of the module (such as the name and version),
+# or affect behavior of the current module and its dependents.
+module(
+ name = "example_bzlmod_build_file_generation",
+ version = "0.0.0",
+ compatibility_level = 1,
+)
+
+# The following stanza defines the dependency rules_python.
+# For typical setups you set the version.
+# See the releases page for available versions.
+# https://github.com/bazelbuild/rules_python/releases
+bazel_dep(name = "rules_python", version = "0.0.0")
+
+# The following loads rules_python from the file system.
+# For usual setups you should remove this local_path_override block.
+local_path_override(
+ module_name = "rules_python",
+ path = "../..",
+)
+
+# The following stanza defines the dependency rules_python_gazelle_plugin.
+# For typical setups you set the version.
+# See the releases page for available versions.
+# https://github.com/bazelbuild/rules_python/releases
+bazel_dep(name = "rules_python_gazelle_plugin", version = "0.0.0")
+
+# The following starlark loads the gazelle plugin from the file system.
+# For usual setups you should remove this local_path_override block.
+local_path_override(
+ module_name = "rules_python_gazelle_plugin",
+ path = "../../gazelle",
+)
+
+# The following stanza defines the dependency for gazelle
+# See here https://github.com/bazelbuild/bazel-gazelle/releases/ for the
+# latest version.
+bazel_dep(name = "gazelle", version = "0.30.0", repo_name = "bazel_gazelle")
+
+# The following stanze returns a proxy object representing a module extension;
+# its methods can be invoked to create module extension tags.
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
+
+# We next initialize the python toolchain using the extension.
+# You can set different Python versions in this block.
+python.toolchain(
+ configure_coverage_tool = True,
+ is_default = True,
+ python_version = "3.9",
+)
+
+# Use the extension, pip.parse, to call the `pip_repository` rule that invokes
+# `pip`, with `incremental` set. The pip call accepts a locked/compiled
+# requirements file and installs the dependencies listed within.
+# Those dependencies become available in a generated `requirements.bzl` file.
+# You can instead check this `requirements.bzl` file into your repo.
+# Because this project has different requirements for windows vs other
+# operating systems, we have requirements for each.
+pip = use_extension("@rules_python//python/extensions:pip.bzl", "pip")
+pip.parse(
+ hub_name = "pip",
+ # The interpreter_target attribute points to the interpreter to
+ # use for running pip commands to download the packages in the
+ # requirements file.
+ # As a best practice, we use the same interpreter as the toolchain
+ # that was configured above; this ensures the same Python version
+ # is used for both resolving dependencies and running tests/binaries.
+ # If this isn't specified, then you'll get whatever is locally installed
+ # on your system.
+ python_version = "3.9",
+ requirements_lock = "//:requirements_lock.txt",
+ requirements_windows = "//:requirements_windows.txt",
+)
+
+# Imports the pip toolchain generated by the given module extension into the scope of the current module.
+use_repo(pip, "pip")
+
+# This project includes a different module that is on the local file system.
+# Add the module to this parent project.
+bazel_dep(name = "other_module", version = "", repo_name = "our_other_module")
+local_path_override(
+ module_name = "other_module",
+ path = "other_module",
+)
diff --git a/examples/bzlmod_build_file_generation/README.md b/examples/bzlmod_build_file_generation/README.md
new file mode 100644
index 0000000..703fd38
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/README.md
@@ -0,0 +1,28 @@
+# Bzlmod build file generation example
+
+This example demostrates how to use `rules_python` and gazelle with `bzlmod` enabled.
+[Bzlmod](https://bazel.build/external/overview#bzlmod), the new external dependency
+subsystem, does not directly work with repo definitions. Instead, it builds a dependency
+graph from modules, runs extensions on top of the graph, and defines repos accordingly.
+
+Gazelle is setup with the `rules_python`
+extension, so that targets like `py_library` and `py_binary` can be
+automatically created just by running:
+
+```sh
+$ bazel run //:gazelle update
+```
+
+The are other targets that allow you to update the gazelle dependency management
+when you update the requirements.in file. See:
+
+```bash
+bazel run //:gazelle_python_manifest.update
+```
+
+For more information on the behavior of the `rules_python` gazelle extension,
+see the [README.md](../../gazelle/README.md) file in the /gazelle folder.
+
+This example uses a `MODULE.bazel` file that configures the bzlmod dependency
+management. See comments in the `MODULE.bazel` and `BUILD.bazel` files for more
+information.
diff --git a/examples/bzlmod_build_file_generation/WORKSPACE b/examples/bzlmod_build_file_generation/WORKSPACE
new file mode 100644
index 0000000..78cc252
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/WORKSPACE
@@ -0,0 +1,2 @@
+# Empty file indicating the root of a Bazel workspace.
+# Dependencies and setup are in MODULE.bazel.
diff --git a/examples/bzlmod_build_file_generation/__main__.py b/examples/bzlmod_build_file_generation/__main__.py
new file mode 100644
index 0000000..099493b
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/__main__.py
@@ -0,0 +1,18 @@
+# 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.
+
+from lib import main
+
+if __name__ == "__main__":
+ print(main([["A", 1], ["B", 2]]))
diff --git a/examples/bzlmod_build_file_generation/__test__.py b/examples/bzlmod_build_file_generation/__test__.py
new file mode 100644
index 0000000..cdc1c89
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/__test__.py
@@ -0,0 +1,33 @@
+# 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.
+
+import unittest
+
+from lib import main
+
+
+class ExampleTest(unittest.TestCase):
+ def test_main(self):
+ self.assertEquals(
+ """\
+- -
+A 1
+B 2
+- -""",
+ main([["A", 1], ["B", 2]]),
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/bzlmod_build_file_generation/gazelle_python.yaml b/examples/bzlmod_build_file_generation/gazelle_python.yaml
new file mode 100644
index 0000000..e33021b
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/gazelle_python.yaml
@@ -0,0 +1,591 @@
+# GENERATED FILE - DO NOT EDIT!
+#
+# To update this file, run:
+# bazel run //:gazelle_python_manifest.update
+
+manifest:
+ modules_mapping:
+ S3: s3cmd
+ S3.ACL: s3cmd
+ S3.AccessLog: s3cmd
+ S3.BidirMap: s3cmd
+ S3.CloudFront: s3cmd
+ S3.Config: s3cmd
+ S3.ConnMan: s3cmd
+ S3.Crypto: s3cmd
+ S3.Custom_httplib27: s3cmd
+ S3.Custom_httplib3x: s3cmd
+ S3.Exceptions: s3cmd
+ S3.ExitCodes: s3cmd
+ S3.FileDict: s3cmd
+ S3.FileLists: s3cmd
+ S3.HashCache: s3cmd
+ S3.MultiPart: s3cmd
+ S3.PkgInfo: s3cmd
+ S3.Progress: s3cmd
+ S3.S3: s3cmd
+ S3.S3Uri: s3cmd
+ S3.SortedDict: s3cmd
+ S3.Utils: s3cmd
+ astroid: astroid
+ astroid.arguments: astroid
+ astroid.astroid_manager: astroid
+ astroid.bases: astroid
+ astroid.brain: astroid
+ astroid.brain.brain_argparse: astroid
+ astroid.brain.brain_attrs: astroid
+ astroid.brain.brain_boto3: astroid
+ astroid.brain.brain_builtin_inference: astroid
+ astroid.brain.brain_collections: astroid
+ astroid.brain.brain_crypt: astroid
+ astroid.brain.brain_ctypes: astroid
+ astroid.brain.brain_curses: astroid
+ astroid.brain.brain_dataclasses: astroid
+ astroid.brain.brain_dateutil: astroid
+ astroid.brain.brain_fstrings: astroid
+ astroid.brain.brain_functools: astroid
+ astroid.brain.brain_gi: astroid
+ astroid.brain.brain_hashlib: astroid
+ astroid.brain.brain_http: astroid
+ astroid.brain.brain_hypothesis: astroid
+ astroid.brain.brain_io: astroid
+ astroid.brain.brain_mechanize: astroid
+ astroid.brain.brain_multiprocessing: astroid
+ astroid.brain.brain_namedtuple_enum: astroid
+ astroid.brain.brain_nose: astroid
+ astroid.brain.brain_numpy_core_einsumfunc: astroid
+ astroid.brain.brain_numpy_core_fromnumeric: astroid
+ astroid.brain.brain_numpy_core_function_base: astroid
+ astroid.brain.brain_numpy_core_multiarray: astroid
+ astroid.brain.brain_numpy_core_numeric: astroid
+ astroid.brain.brain_numpy_core_numerictypes: astroid
+ astroid.brain.brain_numpy_core_umath: astroid
+ astroid.brain.brain_numpy_ma: astroid
+ astroid.brain.brain_numpy_ndarray: astroid
+ astroid.brain.brain_numpy_random_mtrand: astroid
+ astroid.brain.brain_numpy_utils: astroid
+ astroid.brain.brain_pathlib: astroid
+ astroid.brain.brain_pkg_resources: astroid
+ astroid.brain.brain_pytest: astroid
+ astroid.brain.brain_qt: astroid
+ astroid.brain.brain_random: astroid
+ astroid.brain.brain_re: astroid
+ astroid.brain.brain_responses: astroid
+ astroid.brain.brain_scipy_signal: astroid
+ astroid.brain.brain_signal: astroid
+ astroid.brain.brain_six: astroid
+ astroid.brain.brain_sqlalchemy: astroid
+ astroid.brain.brain_ssl: astroid
+ astroid.brain.brain_subprocess: astroid
+ astroid.brain.brain_threading: astroid
+ astroid.brain.brain_type: astroid
+ astroid.brain.brain_typing: astroid
+ astroid.brain.brain_unittest: astroid
+ astroid.brain.brain_uuid: astroid
+ astroid.brain.helpers: astroid
+ astroid.builder: astroid
+ astroid.const: astroid
+ astroid.context: astroid
+ astroid.decorators: astroid
+ astroid.exceptions: astroid
+ astroid.filter_statements: astroid
+ astroid.helpers: astroid
+ astroid.inference: astroid
+ astroid.inference_tip: astroid
+ astroid.interpreter: astroid
+ astroid.interpreter.dunder_lookup: astroid
+ astroid.interpreter.objectmodel: astroid
+ astroid.manager: astroid
+ astroid.mixins: astroid
+ astroid.modutils: astroid
+ astroid.node_classes: astroid
+ astroid.nodes: astroid
+ astroid.nodes.as_string: astroid
+ astroid.nodes.const: astroid
+ astroid.nodes.node_classes: astroid
+ astroid.nodes.node_ng: astroid
+ astroid.nodes.scoped_nodes: astroid
+ astroid.nodes.scoped_nodes.mixin: astroid
+ astroid.nodes.scoped_nodes.scoped_nodes: astroid
+ astroid.nodes.scoped_nodes.utils: astroid
+ astroid.nodes.utils: astroid
+ astroid.objects: astroid
+ astroid.protocols: astroid
+ astroid.raw_building: astroid
+ astroid.rebuilder: astroid
+ astroid.scoped_nodes: astroid
+ astroid.test_utils: astroid
+ astroid.transforms: astroid
+ astroid.typing: astroid
+ astroid.util: astroid
+ certifi: certifi
+ certifi.core: certifi
+ chardet: chardet
+ chardet.big5freq: chardet
+ chardet.big5prober: chardet
+ chardet.chardistribution: chardet
+ chardet.charsetgroupprober: chardet
+ chardet.charsetprober: chardet
+ chardet.cli: chardet
+ chardet.cli.chardetect: chardet
+ chardet.codingstatemachine: chardet
+ chardet.compat: chardet
+ chardet.cp949prober: chardet
+ chardet.enums: chardet
+ chardet.escprober: chardet
+ chardet.escsm: chardet
+ chardet.eucjpprober: chardet
+ chardet.euckrfreq: chardet
+ chardet.euckrprober: chardet
+ chardet.euctwfreq: chardet
+ chardet.euctwprober: chardet
+ chardet.gb2312freq: chardet
+ chardet.gb2312prober: chardet
+ chardet.hebrewprober: chardet
+ chardet.jisfreq: chardet
+ chardet.jpcntx: chardet
+ chardet.langbulgarianmodel: chardet
+ chardet.langgreekmodel: chardet
+ chardet.langhebrewmodel: chardet
+ chardet.langhungarianmodel: chardet
+ chardet.langrussianmodel: chardet
+ chardet.langthaimodel: chardet
+ chardet.langturkishmodel: chardet
+ chardet.latin1prober: chardet
+ chardet.mbcharsetprober: chardet
+ chardet.mbcsgroupprober: chardet
+ chardet.mbcssm: chardet
+ chardet.metadata: chardet
+ chardet.metadata.languages: chardet
+ chardet.sbcharsetprober: chardet
+ chardet.sbcsgroupprober: chardet
+ chardet.sjisprober: chardet
+ chardet.universaldetector: chardet
+ chardet.utf8prober: chardet
+ chardet.version: chardet
+ dateutil: python_dateutil
+ dateutil.easter: python_dateutil
+ dateutil.parser: python_dateutil
+ dateutil.parser.isoparser: python_dateutil
+ dateutil.relativedelta: python_dateutil
+ dateutil.rrule: python_dateutil
+ dateutil.tz: python_dateutil
+ dateutil.tz.tz: python_dateutil
+ dateutil.tz.win: python_dateutil
+ dateutil.tzwin: python_dateutil
+ dateutil.utils: python_dateutil
+ dateutil.zoneinfo: python_dateutil
+ dateutil.zoneinfo.rebuild: python_dateutil
+ dill: dill
+ dill.detect: dill
+ dill.logger: dill
+ dill.objtypes: dill
+ dill.pointers: dill
+ dill.session: dill
+ dill.settings: dill
+ dill.source: dill
+ dill.temp: dill
+ idna: idna
+ idna.codec: idna
+ idna.compat: idna
+ idna.core: idna
+ idna.idnadata: idna
+ idna.intranges: idna
+ idna.package_data: idna
+ idna.uts46data: idna
+ isort: isort
+ isort.api: isort
+ isort.comments: isort
+ isort.core: isort
+ isort.deprecated: isort
+ isort.deprecated.finders: isort
+ isort.exceptions: isort
+ isort.files: isort
+ isort.format: isort
+ isort.hooks: isort
+ isort.identify: isort
+ isort.io: isort
+ isort.literal: isort
+ isort.logo: isort
+ isort.main: isort
+ isort.output: isort
+ isort.parse: isort
+ isort.place: isort
+ isort.profiles: isort
+ isort.pylama_isort: isort
+ isort.sections: isort
+ isort.settings: isort
+ isort.setuptools_commands: isort
+ isort.sorting: isort
+ isort.stdlibs: isort
+ isort.stdlibs.all: isort
+ isort.stdlibs.py2: isort
+ isort.stdlibs.py27: isort
+ isort.stdlibs.py3: isort
+ isort.stdlibs.py310: isort
+ isort.stdlibs.py311: isort
+ isort.stdlibs.py36: isort
+ isort.stdlibs.py37: isort
+ isort.stdlibs.py38: isort
+ isort.stdlibs.py39: isort
+ isort.utils: isort
+ isort.wrap: isort
+ isort.wrap_modes: isort
+ lazy_object_proxy: lazy_object_proxy
+ lazy_object_proxy.cext: lazy_object_proxy
+ lazy_object_proxy.compat: lazy_object_proxy
+ lazy_object_proxy.simple: lazy_object_proxy
+ lazy_object_proxy.slots: lazy_object_proxy
+ lazy_object_proxy.utils: lazy_object_proxy
+ magic: python_magic
+ magic.compat: python_magic
+ magic.loader: python_magic
+ mccabe: mccabe
+ pathspec: pathspec
+ pathspec.gitignore: pathspec
+ pathspec.pathspec: pathspec
+ pathspec.pattern: pathspec
+ pathspec.patterns: pathspec
+ pathspec.patterns.gitwildmatch: pathspec
+ pathspec.util: pathspec
+ pkg_resources: setuptools
+ pkg_resources.extern: setuptools
+ platformdirs: platformdirs
+ platformdirs.android: platformdirs
+ platformdirs.api: platformdirs
+ platformdirs.macos: platformdirs
+ platformdirs.unix: platformdirs
+ platformdirs.version: platformdirs
+ platformdirs.windows: platformdirs
+ pylint: pylint
+ pylint.checkers: pylint
+ pylint.checkers.async: pylint
+ pylint.checkers.base: pylint
+ pylint.checkers.base.basic_checker: pylint
+ pylint.checkers.base.basic_error_checker: pylint
+ pylint.checkers.base.comparison_checker: pylint
+ pylint.checkers.base.docstring_checker: pylint
+ pylint.checkers.base.name_checker: pylint
+ pylint.checkers.base.name_checker.checker: pylint
+ pylint.checkers.base.name_checker.naming_style: pylint
+ pylint.checkers.base.pass_checker: pylint
+ pylint.checkers.base_checker: pylint
+ pylint.checkers.classes: pylint
+ pylint.checkers.classes.class_checker: pylint
+ pylint.checkers.classes.special_methods_checker: pylint
+ pylint.checkers.deprecated: pylint
+ pylint.checkers.design_analysis: pylint
+ pylint.checkers.dunder_methods: pylint
+ pylint.checkers.ellipsis_checker: pylint
+ pylint.checkers.exceptions: pylint
+ pylint.checkers.format: pylint
+ pylint.checkers.imports: pylint
+ pylint.checkers.lambda_expressions: pylint
+ pylint.checkers.logging: pylint
+ pylint.checkers.mapreduce_checker: pylint
+ pylint.checkers.method_args: pylint
+ pylint.checkers.misc: pylint
+ pylint.checkers.modified_iterating_checker: pylint
+ pylint.checkers.newstyle: pylint
+ pylint.checkers.non_ascii_names: pylint
+ pylint.checkers.raw_metrics: pylint
+ pylint.checkers.refactoring: pylint
+ pylint.checkers.refactoring.implicit_booleaness_checker: pylint
+ pylint.checkers.refactoring.not_checker: pylint
+ pylint.checkers.refactoring.recommendation_checker: pylint
+ pylint.checkers.refactoring.refactoring_checker: pylint
+ pylint.checkers.similar: pylint
+ pylint.checkers.spelling: pylint
+ pylint.checkers.stdlib: pylint
+ pylint.checkers.strings: pylint
+ pylint.checkers.threading_checker: pylint
+ pylint.checkers.typecheck: pylint
+ pylint.checkers.unicode: pylint
+ pylint.checkers.unsupported_version: pylint
+ pylint.checkers.utils: pylint
+ pylint.checkers.variables: pylint
+ pylint.config: pylint
+ pylint.config.argument: pylint
+ pylint.config.arguments_manager: pylint
+ pylint.config.arguments_provider: pylint
+ pylint.config.callback_actions: pylint
+ pylint.config.config_file_parser: pylint
+ pylint.config.config_initialization: pylint
+ pylint.config.configuration_mixin: pylint
+ pylint.config.deprecation_actions: pylint
+ pylint.config.environment_variable: pylint
+ pylint.config.exceptions: pylint
+ pylint.config.find_default_config_files: pylint
+ pylint.config.help_formatter: pylint
+ pylint.config.option: pylint
+ pylint.config.option_manager_mixin: pylint
+ pylint.config.option_parser: pylint
+ pylint.config.options_provider_mixin: pylint
+ pylint.config.utils: pylint
+ pylint.constants: pylint
+ pylint.epylint: pylint
+ pylint.exceptions: pylint
+ pylint.extensions: pylint
+ pylint.extensions.bad_builtin: pylint
+ pylint.extensions.broad_try_clause: pylint
+ pylint.extensions.check_elif: pylint
+ pylint.extensions.code_style: pylint
+ pylint.extensions.comparetozero: pylint
+ pylint.extensions.comparison_placement: pylint
+ pylint.extensions.confusing_elif: pylint
+ pylint.extensions.consider_ternary_expression: pylint
+ pylint.extensions.docparams: pylint
+ pylint.extensions.docstyle: pylint
+ pylint.extensions.empty_comment: pylint
+ pylint.extensions.emptystring: pylint
+ pylint.extensions.eq_without_hash: pylint
+ pylint.extensions.for_any_all: pylint
+ pylint.extensions.mccabe: pylint
+ pylint.extensions.no_self_use: pylint
+ pylint.extensions.overlapping_exceptions: pylint
+ pylint.extensions.private_import: pylint
+ pylint.extensions.redefined_loop_name: pylint
+ pylint.extensions.redefined_variable_type: pylint
+ pylint.extensions.set_membership: pylint
+ pylint.extensions.typing: pylint
+ pylint.extensions.while_used: pylint
+ pylint.graph: pylint
+ pylint.interfaces: pylint
+ pylint.lint: pylint
+ pylint.lint.base_options: pylint
+ pylint.lint.caching: pylint
+ pylint.lint.expand_modules: pylint
+ pylint.lint.message_state_handler: pylint
+ pylint.lint.parallel: pylint
+ pylint.lint.pylinter: pylint
+ pylint.lint.report_functions: pylint
+ pylint.lint.run: pylint
+ pylint.lint.utils: pylint
+ pylint.message: pylint
+ pylint.message.message: pylint
+ pylint.message.message_definition: pylint
+ pylint.message.message_definition_store: pylint
+ pylint.message.message_id_store: pylint
+ pylint.pyreverse: pylint
+ pylint.pyreverse.diadefslib: pylint
+ pylint.pyreverse.diagrams: pylint
+ pylint.pyreverse.dot_printer: pylint
+ pylint.pyreverse.inspector: pylint
+ pylint.pyreverse.main: pylint
+ pylint.pyreverse.mermaidjs_printer: pylint
+ pylint.pyreverse.plantuml_printer: pylint
+ pylint.pyreverse.printer: pylint
+ pylint.pyreverse.printer_factory: pylint
+ pylint.pyreverse.utils: pylint
+ pylint.pyreverse.vcg_printer: pylint
+ pylint.pyreverse.writer: pylint
+ pylint.reporters: pylint
+ pylint.reporters.base_reporter: pylint
+ pylint.reporters.collecting_reporter: pylint
+ pylint.reporters.json_reporter: pylint
+ pylint.reporters.multi_reporter: pylint
+ pylint.reporters.reports_handler_mix_in: pylint
+ pylint.reporters.text: pylint
+ pylint.reporters.ureports: pylint
+ pylint.reporters.ureports.base_writer: pylint
+ pylint.reporters.ureports.nodes: pylint
+ pylint.reporters.ureports.text_writer: pylint
+ pylint.testutils: pylint
+ pylint.testutils.checker_test_case: pylint
+ pylint.testutils.configuration_test: pylint
+ pylint.testutils.constants: pylint
+ pylint.testutils.decorator: pylint
+ pylint.testutils.functional: pylint
+ pylint.testutils.functional.find_functional_tests: pylint
+ pylint.testutils.functional.lint_module_output_update: pylint
+ pylint.testutils.functional.test_file: pylint
+ pylint.testutils.functional_test_file: pylint
+ pylint.testutils.get_test_info: pylint
+ pylint.testutils.global_test_linter: pylint
+ pylint.testutils.lint_module_test: pylint
+ pylint.testutils.output_line: pylint
+ pylint.testutils.pyreverse: pylint
+ pylint.testutils.reporter_for_tests: pylint
+ pylint.testutils.tokenize_str: pylint
+ pylint.testutils.unittest_linter: pylint
+ pylint.testutils.utils: pylint
+ pylint.typing: pylint
+ pylint.utils: pylint
+ pylint.utils.ast_walker: pylint
+ pylint.utils.docs: pylint
+ pylint.utils.file_state: pylint
+ pylint.utils.linterstats: pylint
+ pylint.utils.pragma_parser: pylint
+ pylint.utils.utils: pylint
+ requests: requests
+ requests.adapters: requests
+ requests.api: requests
+ requests.auth: requests
+ requests.certs: requests
+ requests.compat: requests
+ requests.cookies: requests
+ requests.exceptions: requests
+ requests.help: requests
+ requests.hooks: requests
+ requests.models: requests
+ requests.packages: requests
+ requests.sessions: requests
+ requests.status_codes: requests
+ requests.structures: requests
+ requests.utils: requests
+ setuptools: setuptools
+ setuptools.archive_util: setuptools
+ setuptools.build_meta: setuptools
+ setuptools.command: setuptools
+ setuptools.command.alias: setuptools
+ setuptools.command.bdist_egg: setuptools
+ setuptools.command.bdist_rpm: setuptools
+ setuptools.command.build: setuptools
+ setuptools.command.build_clib: setuptools
+ setuptools.command.build_ext: setuptools
+ setuptools.command.build_py: setuptools
+ setuptools.command.develop: setuptools
+ setuptools.command.dist_info: setuptools
+ setuptools.command.easy_install: setuptools
+ setuptools.command.editable_wheel: setuptools
+ setuptools.command.egg_info: setuptools
+ setuptools.command.install: setuptools
+ setuptools.command.install_egg_info: setuptools
+ setuptools.command.install_lib: setuptools
+ setuptools.command.install_scripts: setuptools
+ setuptools.command.py36compat: setuptools
+ setuptools.command.register: setuptools
+ setuptools.command.rotate: setuptools
+ setuptools.command.saveopts: setuptools
+ setuptools.command.sdist: setuptools
+ setuptools.command.setopt: setuptools
+ setuptools.command.test: setuptools
+ setuptools.command.upload: setuptools
+ setuptools.command.upload_docs: setuptools
+ setuptools.config: setuptools
+ setuptools.config.expand: setuptools
+ setuptools.config.pyprojecttoml: setuptools
+ setuptools.config.setupcfg: setuptools
+ setuptools.dep_util: setuptools
+ setuptools.depends: setuptools
+ setuptools.discovery: setuptools
+ setuptools.dist: setuptools
+ setuptools.errors: setuptools
+ setuptools.extension: setuptools
+ setuptools.extern: setuptools
+ setuptools.glob: setuptools
+ setuptools.installer: setuptools
+ setuptools.launch: setuptools
+ setuptools.logging: setuptools
+ setuptools.monkey: setuptools
+ setuptools.msvc: setuptools
+ setuptools.namespaces: setuptools
+ setuptools.package_index: setuptools
+ setuptools.py34compat: setuptools
+ setuptools.sandbox: setuptools
+ setuptools.unicode_utils: setuptools
+ setuptools.version: setuptools
+ setuptools.wheel: setuptools
+ setuptools.windows_support: setuptools
+ six: six
+ tabulate: tabulate
+ tabulate.version: tabulate
+ tomli: tomli
+ tomlkit: tomlkit
+ tomlkit.api: tomlkit
+ tomlkit.container: tomlkit
+ tomlkit.exceptions: tomlkit
+ tomlkit.items: tomlkit
+ tomlkit.parser: tomlkit
+ tomlkit.source: tomlkit
+ tomlkit.toml_char: tomlkit
+ tomlkit.toml_document: tomlkit
+ tomlkit.toml_file: tomlkit
+ typing_extensions: typing_extensions
+ urllib3: urllib3
+ urllib3.connection: urllib3
+ urllib3.connectionpool: urllib3
+ urllib3.contrib: urllib3
+ urllib3.contrib.appengine: urllib3
+ urllib3.contrib.ntlmpool: urllib3
+ urllib3.contrib.pyopenssl: urllib3
+ urllib3.contrib.securetransport: urllib3
+ urllib3.contrib.socks: urllib3
+ urllib3.exceptions: urllib3
+ urllib3.fields: urllib3
+ urllib3.filepost: urllib3
+ urllib3.packages: urllib3
+ urllib3.packages.backports: urllib3
+ urllib3.packages.backports.makefile: urllib3
+ urllib3.packages.six: urllib3
+ urllib3.poolmanager: urllib3
+ urllib3.request: urllib3
+ urllib3.response: urllib3
+ urllib3.util: urllib3
+ urllib3.util.connection: urllib3
+ urllib3.util.proxy: urllib3
+ urllib3.util.queue: urllib3
+ urllib3.util.request: urllib3
+ urllib3.util.response: urllib3
+ urllib3.util.retry: urllib3
+ urllib3.util.ssl_: urllib3
+ urllib3.util.ssl_match_hostname: urllib3
+ urllib3.util.ssltransport: urllib3
+ urllib3.util.timeout: urllib3
+ urllib3.util.url: urllib3
+ urllib3.util.wait: urllib3
+ wrapt: wrapt
+ wrapt.arguments: wrapt
+ wrapt.decorators: wrapt
+ wrapt.importer: wrapt
+ wrapt.wrappers: wrapt
+ yaml: PyYAML
+ yaml.composer: PyYAML
+ yaml.constructor: PyYAML
+ yaml.cyaml: PyYAML
+ yaml.dumper: PyYAML
+ yaml.emitter: PyYAML
+ yaml.error: PyYAML
+ yaml.events: PyYAML
+ yaml.loader: PyYAML
+ yaml.nodes: PyYAML
+ yaml.parser: PyYAML
+ yaml.reader: PyYAML
+ yaml.representer: PyYAML
+ yaml.resolver: PyYAML
+ yaml.scanner: PyYAML
+ yaml.serializer: PyYAML
+ yaml.tokens: PyYAML
+ yamllint: yamllint
+ yamllint.cli: yamllint
+ yamllint.config: yamllint
+ yamllint.linter: yamllint
+ yamllint.parser: yamllint
+ yamllint.rules: yamllint
+ yamllint.rules.braces: yamllint
+ yamllint.rules.brackets: yamllint
+ yamllint.rules.colons: yamllint
+ yamllint.rules.commas: yamllint
+ yamllint.rules.comments: yamllint
+ yamllint.rules.comments_indentation: yamllint
+ yamllint.rules.common: yamllint
+ yamllint.rules.document_end: yamllint
+ yamllint.rules.document_start: yamllint
+ yamllint.rules.empty_lines: yamllint
+ yamllint.rules.empty_values: yamllint
+ yamllint.rules.float_values: yamllint
+ yamllint.rules.hyphens: yamllint
+ yamllint.rules.indentation: yamllint
+ yamllint.rules.key_duplicates: yamllint
+ yamllint.rules.key_ordering: yamllint
+ yamllint.rules.line_length: yamllint
+ yamllint.rules.new_line_at_end_of_file: yamllint
+ yamllint.rules.new_lines: yamllint
+ yamllint.rules.octal_values: yamllint
+ yamllint.rules.quoted_strings: yamllint
+ yamllint.rules.trailing_spaces: yamllint
+ yamllint.rules.truthy: yamllint
+ pip_repository:
+ name: pip
+ use_pip_repository_aliases: true
+integrity: cee7684391c4a8a1ff219cd354deae61cdcdee70f2076789aabd5249f3c4eca9
diff --git a/examples/bzlmod_build_file_generation/lib.py b/examples/bzlmod_build_file_generation/lib.py
new file mode 100644
index 0000000..646c6e8
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/lib.py
@@ -0,0 +1,19 @@
+# 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.
+
+from tabulate import tabulate
+
+
+def main(table):
+ return tabulate(table)
diff --git a/examples/bzlmod_build_file_generation/other_module/MODULE.bazel b/examples/bzlmod_build_file_generation/other_module/MODULE.bazel
new file mode 100644
index 0000000..992e120
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/other_module/MODULE.bazel
@@ -0,0 +1,5 @@
+module(
+ name = "other_module",
+)
+
+bazel_dep(name = "rules_python", version = "")
diff --git a/examples/bzlmod_build_file_generation/other_module/WORKSPACE b/examples/bzlmod_build_file_generation/other_module/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/other_module/WORKSPACE
diff --git a/examples/bzlmod_build_file_generation/other_module/other_module/pkg/BUILD.bazel b/examples/bzlmod_build_file_generation/other_module/other_module/pkg/BUILD.bazel
new file mode 100644
index 0000000..9a130e3
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/other_module/other_module/pkg/BUILD.bazel
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "lib",
+ srcs = ["lib.py"],
+ data = ["data/data.txt"],
+ visibility = ["//visibility:public"],
+ deps = ["@rules_python//python/runfiles"],
+)
+
+exports_files(["data/data.txt"])
diff --git a/examples/bzlmod_build_file_generation/other_module/other_module/pkg/data/data.txt b/examples/bzlmod_build_file_generation/other_module/other_module/pkg/data/data.txt
new file mode 100644
index 0000000..e975eaf
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/other_module/other_module/pkg/data/data.txt
@@ -0,0 +1 @@
+Hello, other_module!
diff --git a/examples/bzlmod_build_file_generation/other_module/other_module/pkg/lib.py b/examples/bzlmod_build_file_generation/other_module/other_module/pkg/lib.py
new file mode 100644
index 0000000..eaf65fb
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/other_module/other_module/pkg/lib.py
@@ -0,0 +1,27 @@
+# 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.
+
+from python.runfiles import runfiles
+
+
+def GetRunfilePathWithCurrentRepository():
+ r = runfiles.Create()
+ own_repo = r.CurrentRepository()
+ # For a non-main repository, the name of the runfiles directory is equal to
+ # the canonical repository name.
+ return r.Rlocation(own_repo + "/other_module/pkg/data/data.txt")
+
+
+def GetRunfilePathWithRepoMapping():
+ return runfiles.Create().Rlocation("other_module/other_module/pkg/data/data.txt")
diff --git a/examples/bzlmod_build_file_generation/requirements.in b/examples/bzlmod_build_file_generation/requirements.in
new file mode 100644
index 0000000..a709195
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/requirements.in
@@ -0,0 +1,6 @@
+requests~=2.25.1
+s3cmd~=2.1.0
+yamllint>=1.28.0
+tabulate~=0.9.0
+pylint~=2.15.5
+python-dateutil>=2.8.2
diff --git a/examples/bzlmod_build_file_generation/requirements_lock.txt b/examples/bzlmod_build_file_generation/requirements_lock.txt
new file mode 100644
index 0000000..2160fe1
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/requirements_lock.txt
@@ -0,0 +1,227 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+astroid==2.12.13 \
+ --hash=sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907 \
+ --hash=sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7
+ # via pylint
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+dill==0.3.6 \
+ --hash=sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0 \
+ --hash=sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373
+ # via pylint
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+isort==5.11.4 \
+ --hash=sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6 \
+ --hash=sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b
+ # via pylint
+lazy-object-proxy==1.8.0 \
+ --hash=sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada \
+ --hash=sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d \
+ --hash=sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7 \
+ --hash=sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe \
+ --hash=sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd \
+ --hash=sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c \
+ --hash=sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858 \
+ --hash=sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288 \
+ --hash=sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec \
+ --hash=sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f \
+ --hash=sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891 \
+ --hash=sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c \
+ --hash=sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25 \
+ --hash=sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156 \
+ --hash=sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8 \
+ --hash=sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f \
+ --hash=sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e \
+ --hash=sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0 \
+ --hash=sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b
+ # via astroid
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+pathspec==0.10.3 \
+ --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \
+ --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6
+ # via yamllint
+platformdirs==2.6.0 \
+ --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \
+ --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e
+ # via pylint
+pylint==2.15.9 \
+ --hash=sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4 \
+ --hash=sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb
+ # via -r requirements.in
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # -r requirements.in
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+tabulate==0.9.0 \
+ --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
+ --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via pylint
+tomlkit==0.11.6 \
+ --hash=sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b \
+ --hash=sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73
+ # via pylint
+typing-extensions==4.4.0 \
+ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
+ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
+ # via
+ # astroid
+ # pylint
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
+ # via requests
+wrapt==1.14.1 \
+ --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \
+ --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \
+ --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \
+ --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \
+ --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \
+ --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \
+ --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \
+ --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \
+ --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \
+ --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \
+ --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \
+ --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \
+ --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \
+ --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \
+ --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \
+ --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \
+ --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \
+ --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \
+ --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \
+ --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \
+ --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \
+ --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \
+ --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \
+ --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \
+ --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \
+ --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \
+ --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \
+ --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \
+ --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \
+ --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \
+ --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \
+ --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \
+ --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \
+ --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \
+ --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \
+ --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \
+ --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \
+ --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \
+ --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \
+ --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \
+ --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \
+ --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \
+ --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \
+ --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \
+ --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \
+ --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \
+ --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \
+ --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \
+ --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \
+ --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \
+ --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \
+ --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \
+ --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \
+ --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \
+ --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \
+ --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \
+ --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \
+ --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \
+ --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \
+ --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \
+ --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \
+ --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \
+ --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \
+ --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af
+ # via astroid
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
+ # via -r requirements.in
diff --git a/examples/bzlmod_build_file_generation/requirements_windows.txt b/examples/bzlmod_build_file_generation/requirements_windows.txt
new file mode 100644
index 0000000..06cfdc3
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/requirements_windows.txt
@@ -0,0 +1,231 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+astroid==2.12.13 \
+ --hash=sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907 \
+ --hash=sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7
+ # via pylint
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+colorama==0.4.6 \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+ # via pylint
+dill==0.3.6 \
+ --hash=sha256:a07ffd2351b8c678dfc4a856a3005f8067aea51d6ba6c700796a4d9e280f39f0 \
+ --hash=sha256:e5db55f3687856d8fbdab002ed78544e1c4559a130302693d839dfe8f93f2373
+ # via pylint
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+isort==5.11.4 \
+ --hash=sha256:6db30c5ded9815d813932c04c2f85a360bcdd35fed496f4d8f35495ef0a261b6 \
+ --hash=sha256:c033fd0edb91000a7f09527fe5c75321878f98322a77ddcc81adbd83724afb7b
+ # via pylint
+lazy-object-proxy==1.8.0 \
+ --hash=sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada \
+ --hash=sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d \
+ --hash=sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7 \
+ --hash=sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe \
+ --hash=sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd \
+ --hash=sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c \
+ --hash=sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858 \
+ --hash=sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288 \
+ --hash=sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec \
+ --hash=sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f \
+ --hash=sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891 \
+ --hash=sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c \
+ --hash=sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25 \
+ --hash=sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156 \
+ --hash=sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8 \
+ --hash=sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f \
+ --hash=sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e \
+ --hash=sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0 \
+ --hash=sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b
+ # via astroid
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+pathspec==0.10.3 \
+ --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \
+ --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6
+ # via yamllint
+platformdirs==2.6.0 \
+ --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \
+ --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e
+ # via pylint
+pylint==2.15.9 \
+ --hash=sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4 \
+ --hash=sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb
+ # via -r requirements.in
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # -r requirements.in
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+tabulate==0.9.0 \
+ --hash=sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c \
+ --hash=sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f
+ # via -r requirements.in
+tomli==2.0.1 \
+ --hash=sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc \
+ --hash=sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f
+ # via pylint
+tomlkit==0.11.6 \
+ --hash=sha256:07de26b0d8cfc18f871aec595fda24d95b08fef89d147caa861939f37230bf4b \
+ --hash=sha256:71b952e5721688937fb02cf9d354dbcf0785066149d2855e44531ebdd2b65d73
+ # via pylint
+typing-extensions==4.4.0 \
+ --hash=sha256:1511434bb92bf8dd198c12b1cc812e800d4181cfcb867674e0f8279cc93087aa \
+ --hash=sha256:16fa4864408f655d35ec496218b85f79b3437c829e93320c7c9215ccfd92489e
+ # via
+ # astroid
+ # pylint
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
+ # via requests
+wrapt==1.14.1 \
+ --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \
+ --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \
+ --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \
+ --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \
+ --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \
+ --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \
+ --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \
+ --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \
+ --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \
+ --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \
+ --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \
+ --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \
+ --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \
+ --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \
+ --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \
+ --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \
+ --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \
+ --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \
+ --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \
+ --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \
+ --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \
+ --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \
+ --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \
+ --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \
+ --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \
+ --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \
+ --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \
+ --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \
+ --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \
+ --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \
+ --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \
+ --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \
+ --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \
+ --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \
+ --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \
+ --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \
+ --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \
+ --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \
+ --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \
+ --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \
+ --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \
+ --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \
+ --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \
+ --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \
+ --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \
+ --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \
+ --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \
+ --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \
+ --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \
+ --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \
+ --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \
+ --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \
+ --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \
+ --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \
+ --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \
+ --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \
+ --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \
+ --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \
+ --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \
+ --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \
+ --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \
+ --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \
+ --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \
+ --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af
+ # via astroid
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
+ # via -r requirements.in
diff --git a/examples/bzlmod_build_file_generation/runfiles/BUILD.bazel b/examples/bzlmod_build_file_generation/runfiles/BUILD.bazel
new file mode 100644
index 0000000..3503ac3
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/runfiles/BUILD.bazel
@@ -0,0 +1,19 @@
+load("@rules_python//python:defs.bzl", "py_test")
+
+# gazelle:ignore
+py_test(
+ name = "runfiles_test",
+ srcs = ["runfiles_test.py"],
+ data = [
+ "data/data.txt",
+ "@our_other_module//other_module/pkg:data/data.txt",
+ ],
+ env = {
+ "DATA_RLOCATIONPATH": "$(rlocationpath data/data.txt)",
+ "OTHER_MODULE_DATA_RLOCATIONPATH": "$(rlocationpath @our_other_module//other_module/pkg:data/data.txt)",
+ },
+ deps = [
+ "@our_other_module//other_module/pkg:lib",
+ "@rules_python//python/runfiles",
+ ],
+)
diff --git a/examples/bzlmod_build_file_generation/runfiles/data/data.txt b/examples/bzlmod_build_file_generation/runfiles/data/data.txt
new file mode 100644
index 0000000..fb17e0d
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/runfiles/data/data.txt
@@ -0,0 +1 @@
+Hello, example_bzlmod!
diff --git a/examples/bzlmod_build_file_generation/runfiles/runfiles_test.py b/examples/bzlmod_build_file_generation/runfiles/runfiles_test.py
new file mode 100644
index 0000000..a588040
--- /dev/null
+++ b/examples/bzlmod_build_file_generation/runfiles/runfiles_test.py
@@ -0,0 +1,64 @@
+# 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.
+
+import os
+import unittest
+
+from other_module.pkg import lib
+
+from python.runfiles import runfiles
+
+
+class RunfilesTest(unittest.TestCase):
+ # """Unit tests for `runfiles.Runfiles`."""
+ def testCurrentRepository(self):
+ self.assertEqual(runfiles.Create().CurrentRepository(), "")
+
+ def testRunfilesWithRepoMapping(self):
+ data_path = runfiles.Create().Rlocation("example_bzlmod_build_file_generation/runfiles/data/data.txt")
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, example_bzlmod!")
+
+ def testRunfileWithRlocationpath(self):
+ data_rlocationpath = os.getenv("DATA_RLOCATIONPATH")
+ data_path = runfiles.Create().Rlocation(data_rlocationpath)
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, example_bzlmod!")
+
+ def testRunfileInOtherModuleWithOurRepoMapping(self):
+ data_path = runfiles.Create().Rlocation(
+ "our_other_module/other_module/pkg/data/data.txt"
+ )
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+ def testRunfileInOtherModuleWithItsRepoMapping(self):
+ data_path = lib.GetRunfilePathWithRepoMapping()
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+ def testRunfileInOtherModuleWithCurrentRepository(self):
+ data_path = lib.GetRunfilePathWithCurrentRepository()
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+ def testRunfileInOtherModuleWithRlocationpath(self):
+ data_rlocationpath = os.getenv("OTHER_MODULE_DATA_RLOCATIONPATH")
+ data_path = runfiles.Create().Rlocation(data_rlocationpath)
+ with open(data_path) as f:
+ self.assertEqual(f.read().strip(), "Hello, other_module!")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/multi_python_versions/.bazelrc b/examples/multi_python_versions/.bazelrc
new file mode 100644
index 0000000..3fd6365
--- /dev/null
+++ b/examples/multi_python_versions/.bazelrc
@@ -0,0 +1,7 @@
+test --test_output=errors
+
+# Windows requires these for multi-python support:
+build --enable_runfiles
+startup --windows_enable_symlinks
+
+coverage --java_runtime_version=remotejdk_11
diff --git a/examples/multi_python_versions/.gitignore b/examples/multi_python_versions/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/examples/multi_python_versions/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/examples/multi_python_versions/WORKSPACE b/examples/multi_python_versions/WORKSPACE
new file mode 100644
index 0000000..35855ca
--- /dev/null
+++ b/examples/multi_python_versions/WORKSPACE
@@ -0,0 +1,55 @@
+workspace(name = "rules_python_multi_python_versions")
+
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_multi_toolchains")
+
+py_repositories()
+
+load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies")
+
+pip_install_dependencies()
+
+default_python_version = "3.9"
+
+python_register_multi_toolchains(
+ name = "python",
+ default_version = default_python_version,
+ python_versions = [
+ "3.8",
+ "3.9",
+ "3.10",
+ "3.11",
+ ],
+ register_coverage_tool = True,
+)
+
+load("@python//:pip.bzl", "multi_pip_parse")
+load("@python//3.10:defs.bzl", interpreter_3_10 = "interpreter")
+load("@python//3.11:defs.bzl", interpreter_3_11 = "interpreter")
+load("@python//3.8:defs.bzl", interpreter_3_8 = "interpreter")
+load("@python//3.9:defs.bzl", interpreter_3_9 = "interpreter")
+
+multi_pip_parse(
+ name = "pypi",
+ default_version = default_python_version,
+ python_interpreter_target = {
+ "3.10": interpreter_3_10,
+ "3.11": interpreter_3_11,
+ "3.8": interpreter_3_8,
+ "3.9": interpreter_3_9,
+ },
+ requirements_lock = {
+ "3.10": "//requirements:requirements_lock_3_10.txt",
+ "3.11": "//requirements:requirements_lock_3_11.txt",
+ "3.8": "//requirements:requirements_lock_3_8.txt",
+ "3.9": "//requirements:requirements_lock_3_9.txt",
+ },
+)
+
+load("@pypi//:requirements.bzl", "install_deps")
+
+install_deps()
diff --git a/examples/multi_python_versions/libs/my_lib/BUILD.bazel b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
new file mode 100644
index 0000000..8c29f60
--- /dev/null
+++ b/examples/multi_python_versions/libs/my_lib/BUILD.bazel
@@ -0,0 +1,9 @@
+load("@pypi//:requirements.bzl", "requirement")
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "my_lib",
+ srcs = ["__init__.py"],
+ visibility = ["@//tests:__pkg__"],
+ deps = [requirement("websockets")],
+)
diff --git a/examples/multi_python_versions/libs/my_lib/__init__.py b/examples/multi_python_versions/libs/my_lib/__init__.py
new file mode 100644
index 0000000..33cfb41
--- /dev/null
+++ b/examples/multi_python_versions/libs/my_lib/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import websockets
+
+
+def websockets_is_for_python_version(sanitized_version_check):
+ return f"pypi_{sanitized_version_check}_websockets" in websockets.__file__
diff --git a/examples/multi_python_versions/requirements/BUILD.bazel b/examples/multi_python_versions/requirements/BUILD.bazel
new file mode 100644
index 0000000..e3184c8
--- /dev/null
+++ b/examples/multi_python_versions/requirements/BUILD.bazel
@@ -0,0 +1,32 @@
+load("@python//3.10:defs.bzl", compile_pip_requirements_3_10 = "compile_pip_requirements")
+load("@python//3.11:defs.bzl", compile_pip_requirements_3_11 = "compile_pip_requirements")
+load("@python//3.8:defs.bzl", compile_pip_requirements_3_8 = "compile_pip_requirements")
+load("@python//3.9:defs.bzl", compile_pip_requirements_3_9 = "compile_pip_requirements")
+
+compile_pip_requirements_3_8(
+ name = "requirements_3_8",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock_3_8.txt",
+)
+
+compile_pip_requirements_3_9(
+ name = "requirements_3_9",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock_3_9.txt",
+)
+
+compile_pip_requirements_3_10(
+ name = "requirements_3_10",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock_3_10.txt",
+)
+
+compile_pip_requirements_3_11(
+ name = "requirements_3_11",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock_3_11.txt",
+)
diff --git a/examples/multi_python_versions/requirements/requirements.in b/examples/multi_python_versions/requirements/requirements.in
new file mode 100644
index 0000000..14774b4
--- /dev/null
+++ b/examples/multi_python_versions/requirements/requirements.in
@@ -0,0 +1 @@
+websockets
diff --git a/examples/multi_python_versions/requirements/requirements_lock_3_10.txt b/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
new file mode 100644
index 0000000..6bee4e0
--- /dev/null
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
@@ -0,0 +1,56 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# bazel run //requirements:requirements_3_10.update
+#
+websockets==10.3 \
+ --hash=sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af \
+ --hash=sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c \
+ --hash=sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76 \
+ --hash=sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47 \
+ --hash=sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69 \
+ --hash=sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079 \
+ --hash=sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c \
+ --hash=sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55 \
+ --hash=sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02 \
+ --hash=sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559 \
+ --hash=sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3 \
+ --hash=sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e \
+ --hash=sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978 \
+ --hash=sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98 \
+ --hash=sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae \
+ --hash=sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755 \
+ --hash=sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d \
+ --hash=sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991 \
+ --hash=sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1 \
+ --hash=sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680 \
+ --hash=sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247 \
+ --hash=sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f \
+ --hash=sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2 \
+ --hash=sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7 \
+ --hash=sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4 \
+ --hash=sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667 \
+ --hash=sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb \
+ --hash=sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094 \
+ --hash=sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36 \
+ --hash=sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79 \
+ --hash=sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500 \
+ --hash=sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e \
+ --hash=sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582 \
+ --hash=sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442 \
+ --hash=sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd \
+ --hash=sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6 \
+ --hash=sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731 \
+ --hash=sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4 \
+ --hash=sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d \
+ --hash=sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8 \
+ --hash=sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f \
+ --hash=sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677 \
+ --hash=sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8 \
+ --hash=sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9 \
+ --hash=sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e \
+ --hash=sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b \
+ --hash=sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916 \
+ --hash=sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4
+ # via -r requirements/requirements.in
diff --git a/examples/multi_python_versions/requirements/requirements_lock_3_11.txt b/examples/multi_python_versions/requirements/requirements_lock_3_11.txt
new file mode 100644
index 0000000..a437a39
--- /dev/null
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_11.txt
@@ -0,0 +1,56 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# bazel run //requirements:requirements_3_11.update
+#
+websockets==10.3 \
+ --hash=sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af \
+ --hash=sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c \
+ --hash=sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76 \
+ --hash=sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47 \
+ --hash=sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69 \
+ --hash=sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079 \
+ --hash=sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c \
+ --hash=sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55 \
+ --hash=sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02 \
+ --hash=sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559 \
+ --hash=sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3 \
+ --hash=sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e \
+ --hash=sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978 \
+ --hash=sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98 \
+ --hash=sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae \
+ --hash=sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755 \
+ --hash=sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d \
+ --hash=sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991 \
+ --hash=sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1 \
+ --hash=sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680 \
+ --hash=sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247 \
+ --hash=sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f \
+ --hash=sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2 \
+ --hash=sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7 \
+ --hash=sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4 \
+ --hash=sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667 \
+ --hash=sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb \
+ --hash=sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094 \
+ --hash=sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36 \
+ --hash=sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79 \
+ --hash=sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500 \
+ --hash=sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e \
+ --hash=sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582 \
+ --hash=sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442 \
+ --hash=sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd \
+ --hash=sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6 \
+ --hash=sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731 \
+ --hash=sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4 \
+ --hash=sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d \
+ --hash=sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8 \
+ --hash=sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f \
+ --hash=sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677 \
+ --hash=sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8 \
+ --hash=sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9 \
+ --hash=sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e \
+ --hash=sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b \
+ --hash=sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916 \
+ --hash=sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4
+ # via -r requirements/requirements.in
diff --git a/examples/multi_python_versions/requirements/requirements_lock_3_8.txt b/examples/multi_python_versions/requirements/requirements_lock_3_8.txt
new file mode 100644
index 0000000..19303f8
--- /dev/null
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_8.txt
@@ -0,0 +1,56 @@
+#
+# This file is autogenerated by pip-compile with Python 3.8
+# by the following command:
+#
+# bazel run //requirements:requirements_3_8.update
+#
+websockets==10.3 \
+ --hash=sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af \
+ --hash=sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c \
+ --hash=sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76 \
+ --hash=sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47 \
+ --hash=sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69 \
+ --hash=sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079 \
+ --hash=sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c \
+ --hash=sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55 \
+ --hash=sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02 \
+ --hash=sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559 \
+ --hash=sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3 \
+ --hash=sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e \
+ --hash=sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978 \
+ --hash=sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98 \
+ --hash=sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae \
+ --hash=sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755 \
+ --hash=sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d \
+ --hash=sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991 \
+ --hash=sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1 \
+ --hash=sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680 \
+ --hash=sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247 \
+ --hash=sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f \
+ --hash=sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2 \
+ --hash=sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7 \
+ --hash=sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4 \
+ --hash=sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667 \
+ --hash=sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb \
+ --hash=sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094 \
+ --hash=sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36 \
+ --hash=sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79 \
+ --hash=sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500 \
+ --hash=sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e \
+ --hash=sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582 \
+ --hash=sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442 \
+ --hash=sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd \
+ --hash=sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6 \
+ --hash=sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731 \
+ --hash=sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4 \
+ --hash=sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d \
+ --hash=sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8 \
+ --hash=sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f \
+ --hash=sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677 \
+ --hash=sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8 \
+ --hash=sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9 \
+ --hash=sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e \
+ --hash=sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b \
+ --hash=sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916 \
+ --hash=sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4
+ # via -r requirements/requirements.in
diff --git a/examples/multi_python_versions/requirements/requirements_lock_3_9.txt b/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
new file mode 100644
index 0000000..4af42ca
--- /dev/null
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
@@ -0,0 +1,56 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //requirements:requirements_3_9.update
+#
+websockets==10.3 \
+ --hash=sha256:07cdc0a5b2549bcfbadb585ad8471ebdc7bdf91e32e34ae3889001c1c106a6af \
+ --hash=sha256:210aad7fdd381c52e58777560860c7e6110b6174488ef1d4b681c08b68bf7f8c \
+ --hash=sha256:28dd20b938a57c3124028680dc1600c197294da5db4292c76a0b48efb3ed7f76 \
+ --hash=sha256:2f94fa3ae454a63ea3a19f73b95deeebc9f02ba2d5617ca16f0bbdae375cda47 \
+ --hash=sha256:31564a67c3e4005f27815634343df688b25705cccb22bc1db621c781ddc64c69 \
+ --hash=sha256:347974105bbd4ea068106ec65e8e8ebd86f28c19e529d115d89bd8cc5cda3079 \
+ --hash=sha256:379e03422178436af4f3abe0aa8f401aa77ae2487843738542a75faf44a31f0c \
+ --hash=sha256:3eda1cb7e9da1b22588cefff09f0951771d6ee9fa8dbe66f5ae04cc5f26b2b55 \
+ --hash=sha256:51695d3b199cd03098ae5b42833006a0f43dc5418d3102972addc593a783bc02 \
+ --hash=sha256:54c000abeaff6d8771a4e2cef40900919908ea7b6b6a30eae72752607c6db559 \
+ --hash=sha256:5b936bf552e4f6357f5727579072ff1e1324717902127ffe60c92d29b67b7be3 \
+ --hash=sha256:6075fd24df23133c1b078e08a9b04a3bc40b31a8def4ee0b9f2c8865acce913e \
+ --hash=sha256:661f641b44ed315556a2fa630239adfd77bd1b11cb0b9d96ed8ad90b0b1e4978 \
+ --hash=sha256:6ea6b300a6bdd782e49922d690e11c3669828fe36fc2471408c58b93b5535a98 \
+ --hash=sha256:6ed1d6f791eabfd9808afea1e068f5e59418e55721db8b7f3bfc39dc831c42ae \
+ --hash=sha256:7934e055fd5cd9dee60f11d16c8d79c4567315824bacb1246d0208a47eca9755 \
+ --hash=sha256:7ab36e17af592eec5747c68ef2722a74c1a4a70f3772bc661079baf4ae30e40d \
+ --hash=sha256:7f6d96fdb0975044fdd7953b35d003b03f9e2bcf85f2d2cf86285ece53e9f991 \
+ --hash=sha256:83e5ca0d5b743cde3d29fda74ccab37bdd0911f25bd4cdf09ff8b51b7b4f2fa1 \
+ --hash=sha256:85506b3328a9e083cc0a0fb3ba27e33c8db78341b3eb12eb72e8afd166c36680 \
+ --hash=sha256:8af75085b4bc0b5c40c4a3c0e113fa95e84c60f4ed6786cbb675aeb1ee128247 \
+ --hash=sha256:8b1359aba0ff810d5830d5ab8e2c4a02bebf98a60aa0124fb29aa78cfdb8031f \
+ --hash=sha256:8fbd7d77f8aba46d43245e86dd91a8970eac4fb74c473f8e30e9c07581f852b2 \
+ --hash=sha256:907e8247480f287aa9bbc9391bd6de23c906d48af54c8c421df84655eef66af7 \
+ --hash=sha256:93d5ea0b5da8d66d868b32c614d2b52d14304444e39e13a59566d4acb8d6e2e4 \
+ --hash=sha256:97bc9d41e69a7521a358f9b8e44871f6cdeb42af31815c17aed36372d4eec667 \
+ --hash=sha256:994cdb1942a7a4c2e10098d9162948c9e7b235df755de91ca33f6e0481366fdb \
+ --hash=sha256:a141de3d5a92188234afa61653ed0bbd2dde46ad47b15c3042ffb89548e77094 \
+ --hash=sha256:a1e15b230c3613e8ea82c9fc6941b2093e8eb939dd794c02754d33980ba81e36 \
+ --hash=sha256:aad5e300ab32036eb3fdc350ad30877210e2f51bceaca83fb7fef4d2b6c72b79 \
+ --hash=sha256:b529fdfa881b69fe563dbd98acce84f3e5a67df13de415e143ef053ff006d500 \
+ --hash=sha256:b9c77f0d1436ea4b4dc089ed8335fa141e6a251a92f75f675056dac4ab47a71e \
+ --hash=sha256:bb621ec2dbbbe8df78a27dbd9dd7919f9b7d32a73fafcb4d9252fc4637343582 \
+ --hash=sha256:c7250848ce69559756ad0086a37b82c986cd33c2d344ab87fea596c5ac6d9442 \
+ --hash=sha256:c8d1d14aa0f600b5be363077b621b1b4d1eb3fbf90af83f9281cda668e6ff7fd \
+ --hash=sha256:d1655a6fc7aecd333b079d00fb3c8132d18988e47f19740c69303bf02e9883c6 \
+ --hash=sha256:d6353ba89cfc657a3f5beabb3b69be226adbb5c6c7a66398e17809b0ce3c4731 \
+ --hash=sha256:da4377904a3379f0c1b75a965fff23b28315bcd516d27f99a803720dfebd94d4 \
+ --hash=sha256:e49ea4c1a9543d2bd8a747ff24411509c29e4bdcde05b5b0895e2120cb1a761d \
+ --hash=sha256:e4e08305bfd76ba8edab08dcc6496f40674f44eb9d5e23153efa0a35750337e8 \
+ --hash=sha256:e6fa05a680e35d0fcc1470cb070b10e6fe247af54768f488ed93542e71339d6f \
+ --hash=sha256:e7e6f2d6fd48422071cc8a6f8542016f350b79cc782752de531577d35e9bd677 \
+ --hash=sha256:e904c0381c014b914136c492c8fa711ca4cced4e9b3d110e5e7d436d0fc289e8 \
+ --hash=sha256:ec2b0ab7edc8cd4b0eb428b38ed89079bdc20c6bdb5f889d353011038caac2f9 \
+ --hash=sha256:ef5ce841e102278c1c2e98f043db99d6755b1c58bde475516aef3a008ed7f28e \
+ --hash=sha256:f351c7d7d92f67c0609329ab2735eee0426a03022771b00102816a72715bb00b \
+ --hash=sha256:fab7c640815812ed5f10fbee7abbf58788d602046b7bb3af9b1ac753a6d5e916 \
+ --hash=sha256:fc06cc8073c8e87072138ba1e431300e2d408f054b27047d047b549455066ff4
+ # via -r requirements/requirements.in
diff --git a/examples/multi_python_versions/tests/BUILD.bazel b/examples/multi_python_versions/tests/BUILD.bazel
new file mode 100644
index 0000000..5df41bd
--- /dev/null
+++ b/examples/multi_python_versions/tests/BUILD.bazel
@@ -0,0 +1,184 @@
+load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
+load("@python//3.10:defs.bzl", py_binary_3_10 = "py_binary", py_test_3_10 = "py_test")
+load("@python//3.11:defs.bzl", py_binary_3_11 = "py_binary", py_test_3_11 = "py_test")
+load("@python//3.8:defs.bzl", py_binary_3_8 = "py_binary", py_test_3_8 = "py_test")
+load("@python//3.9:defs.bzl", py_binary_3_9 = "py_binary", py_test_3_9 = "py_test")
+load("@rules_python//python:defs.bzl", "py_binary", "py_test")
+
+copy_file(
+ name = "copy_version",
+ src = "version.py",
+ out = "version_default.py",
+ is_executable = True,
+)
+
+# NOTE: We are testing that the `main` is an optional param as per official
+# docs https://bazel.build/reference/be/python#py_binary.main
+py_binary(
+ name = "version_default",
+ srcs = ["version_default.py"],
+)
+
+py_binary_3_8(
+ name = "version_3_8",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+py_binary_3_9(
+ name = "version_3_9",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+py_binary_3_10(
+ name = "version_3_10",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+py_binary_3_11(
+ name = "version_3_11",
+ srcs = ["version.py"],
+ main = "version.py",
+)
+
+py_test(
+ name = "my_lib_default_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+py_test_3_8(
+ name = "my_lib_3_8_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+py_test_3_9(
+ name = "my_lib_3_9_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+py_test_3_10(
+ name = "my_lib_3_10_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+py_test_3_11(
+ name = "my_lib_3_11_test",
+ srcs = ["my_lib_test.py"],
+ main = "my_lib_test.py",
+ deps = ["//libs/my_lib"],
+)
+
+copy_file(
+ name = "copy_version_test",
+ src = "version_test.py",
+ out = "version_default_test.py",
+ is_executable = True,
+)
+
+py_test(
+ name = "version_default_test",
+ srcs = ["version_default_test.py"],
+ env = {"VERSION_CHECK": "3.9"}, # The default defined in the WORKSPACE.
+)
+
+py_test_3_8(
+ name = "version_3_8_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.8"},
+ main = "version_test.py",
+)
+
+py_test_3_9(
+ name = "version_3_9_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.9"},
+ main = "version_test.py",
+)
+
+py_test_3_10(
+ name = "version_3_10_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.10"},
+ main = "version_test.py",
+)
+
+py_test_3_11(
+ name = "version_3_11_test",
+ srcs = ["version_test.py"],
+ env = {"VERSION_CHECK": "3.11"},
+ main = "version_test.py",
+)
+
+py_test(
+ name = "version_default_takes_3_10_subprocess_test",
+ srcs = ["cross_version_test.py"],
+ data = [":version_3_10"],
+ env = {
+ "SUBPROCESS_VERSION_CHECK": "3.10",
+ "SUBPROCESS_VERSION_PY_BINARY": "$(rootpath :version_3_10)",
+ "VERSION_CHECK": "3.9",
+ },
+ main = "cross_version_test.py",
+)
+
+py_test_3_10(
+ name = "version_3_10_takes_3_9_subprocess_test",
+ srcs = ["cross_version_test.py"],
+ data = [":version_3_9"],
+ env = {
+ "SUBPROCESS_VERSION_CHECK": "3.9",
+ "SUBPROCESS_VERSION_PY_BINARY": "$(rootpath :version_3_9)",
+ "VERSION_CHECK": "3.10",
+ },
+ main = "cross_version_test.py",
+)
+
+sh_test(
+ name = "version_test_binary_default",
+ srcs = ["version_test.sh"],
+ data = [":version_default"],
+ env = {
+ "VERSION_CHECK": "3.9", # The default defined in the WORKSPACE.
+ "VERSION_PY_BINARY": "$(rootpath :version_default)",
+ },
+)
+
+sh_test(
+ name = "version_test_binary_3_8",
+ srcs = ["version_test.sh"],
+ data = [":version_3_8"],
+ env = {
+ "VERSION_CHECK": "3.8",
+ "VERSION_PY_BINARY": "$(rootpath :version_3_8)",
+ },
+)
+
+sh_test(
+ name = "version_test_binary_3_9",
+ srcs = ["version_test.sh"],
+ data = [":version_3_9"],
+ env = {
+ "VERSION_CHECK": "3.9",
+ "VERSION_PY_BINARY": "$(rootpath :version_3_9)",
+ },
+)
+
+sh_test(
+ name = "version_test_binary_3_10",
+ srcs = ["version_test.sh"],
+ data = [":version_3_10"],
+ env = {
+ "VERSION_CHECK": "3.10",
+ "VERSION_PY_BINARY": "$(rootpath :version_3_10)",
+ },
+)
diff --git a/examples/multi_python_versions/tests/cross_version_test.py b/examples/multi_python_versions/tests/cross_version_test.py
new file mode 100644
index 0000000..437be2e
--- /dev/null
+++ b/examples/multi_python_versions/tests/cross_version_test.py
@@ -0,0 +1,39 @@
+# 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.
+
+import os
+import subprocess
+import sys
+
+process = subprocess.run(
+ [os.getenv("SUBPROCESS_VERSION_PY_BINARY")],
+ stdout=subprocess.PIPE,
+ universal_newlines=True,
+)
+
+subprocess_current = process.stdout.strip()
+subprocess_expected = os.getenv("SUBPROCESS_VERSION_CHECK")
+
+if subprocess_current != subprocess_expected:
+ print(
+ f"expected subprocess version '{subprocess_expected}' is different than returned '{subprocess_current}'"
+ )
+ sys.exit(1)
+
+expected = os.getenv("VERSION_CHECK")
+current = f"{sys.version_info.major}.{sys.version_info.minor}"
+
+if current != expected:
+ print(f"expected version '{expected}' is different than returned '{current}'")
+ sys.exit(1)
diff --git a/examples/multi_python_versions/tests/my_lib_test.py b/examples/multi_python_versions/tests/my_lib_test.py
new file mode 100644
index 0000000..e0a97db
--- /dev/null
+++ b/examples/multi_python_versions/tests/my_lib_test.py
@@ -0,0 +1,24 @@
+# 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.
+
+import os
+import sys
+
+import libs.my_lib as my_lib
+
+sanitized_version_check = f"{sys.version_info.major}_{sys.version_info.minor}"
+
+if not my_lib.websockets_is_for_python_version(sanitized_version_check):
+ print("expected package for Python version is different than returned")
+ sys.exit(1)
diff --git a/examples/multi_python_versions/tests/version.py b/examples/multi_python_versions/tests/version.py
new file mode 100644
index 0000000..2d293c1
--- /dev/null
+++ b/examples/multi_python_versions/tests/version.py
@@ -0,0 +1,17 @@
+# 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.
+
+import sys
+
+print(f"{sys.version_info.major}.{sys.version_info.minor}")
diff --git a/examples/multi_python_versions/tests/version_test.py b/examples/multi_python_versions/tests/version_test.py
new file mode 100644
index 0000000..444f5e4
--- /dev/null
+++ b/examples/multi_python_versions/tests/version_test.py
@@ -0,0 +1,23 @@
+# 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.
+
+import os
+import sys
+
+expected = os.getenv("VERSION_CHECK")
+current = f"{sys.version_info.major}.{sys.version_info.minor}"
+
+if current != expected:
+ print(f"expected version '{expected}' is different than returned '{current}'")
+ sys.exit(1)
diff --git a/examples/multi_python_versions/tests/version_test.sh b/examples/multi_python_versions/tests/version_test.sh
new file mode 100755
index 0000000..3bedb95
--- /dev/null
+++ b/examples/multi_python_versions/tests/version_test.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# 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.
+
+
+set -o errexit -o nounset -o pipefail
+
+version_py_binary=$("${VERSION_PY_BINARY}")
+
+if [[ "${version_py_binary}" != "${VERSION_CHECK}" ]]; then
+ echo >&2 "expected version '${VERSION_CHECK}' is different than returned '${version_py_binary}'"
+ exit 1
+fi
diff --git a/examples/pip_install/.bazelrc b/examples/pip_install/.bazelrc
new file mode 100644
index 0000000..9e7ef37
--- /dev/null
+++ b/examples/pip_install/.bazelrc
@@ -0,0 +1,2 @@
+# https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file
+try-import %workspace%/user.bazelrc
diff --git a/examples/pip_install/.gitignore b/examples/pip_install/.gitignore
new file mode 100644
index 0000000..e5ae073
--- /dev/null
+++ b/examples/pip_install/.gitignore
@@ -0,0 +1,4 @@
+# git ignore patterns
+
+/bazel-*
+user.bazelrc
diff --git a/examples/pip_install/BUILD.bazel b/examples/pip_install/BUILD.bazel
new file mode 100644
index 0000000..35f5a93
--- /dev/null
+++ b/examples/pip_install/BUILD.bazel
@@ -0,0 +1,111 @@
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+load(
+ "@pip//:requirements.bzl",
+ "data_requirement",
+ "dist_info_requirement",
+ "entry_point",
+ "requirement",
+)
+load("@rules_python//python:defs.bzl", "py_binary", "py_test")
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+
+# Toolchain setup, this is optional.
+# Demonstrate that we can use the same python interpreter for the toolchain and executing pip in pip install (see WORKSPACE).
+#
+#load("@rules_python//python:defs.bzl", "py_runtime_pair")
+#
+#py_runtime(
+# name = "python3_runtime",
+# files = ["@python_interpreter//:files"],
+# interpreter = "@python_interpreter//:python_bin",
+# python_version = "PY3",
+# visibility = ["//visibility:public"],
+#)
+#
+#py_runtime_pair(
+# name = "my_py_runtime_pair",
+# py2_runtime = None,
+# py3_runtime = ":python3_runtime",
+#)
+#
+#toolchain(
+# name = "my_py_toolchain",
+# toolchain = ":my_py_runtime_pair",
+# toolchain_type = "@bazel_tools//tools/python:toolchain_type",
+#)
+# End of toolchain setup.
+
+py_binary(
+ name = "main",
+ srcs = ["main.py"],
+ deps = [
+ requirement("boto3"),
+ ],
+)
+
+py_test(
+ name = "test",
+ srcs = ["test.py"],
+ deps = [":main"],
+)
+
+# For pip dependencies which have entry points, the `entry_point` macro can be
+# used from the generated `pip_install` repository to access a runnable binary.
+
+alias(
+ name = "yamllint",
+ actual = entry_point("yamllint"),
+)
+
+# Check that our compiled requirements are up-to-date
+compile_pip_requirements(
+ name = "requirements",
+ extra_args = ["--allow-unsafe"],
+ requirements_windows = ":requirements_windows.txt",
+)
+
+# Test the use of all pip_install utilities in a single py_test
+py_test(
+ name = "pip_install_test",
+ srcs = ["pip_install_test.py"],
+ data = [
+ ":yamllint",
+ data_requirement("s3cmd"),
+ dist_info_requirement("boto3"),
+ ],
+ env = {
+ "WHEEL_DATA_CONTENTS": "$(rootpaths {})".format(data_requirement("s3cmd")),
+ "WHEEL_DIST_INFO_CONTENTS": "$(rootpaths {})".format(dist_info_requirement("boto3")),
+ "YAMLLINT_ENTRY_POINT": "$(rootpath :yamllint)",
+ },
+ deps = ["@rules_python//python/runfiles"],
+)
+
+# Assert that tags are present on resulting py_library,
+# which is useful for tooling that needs to reflect on the dep graph
+# to determine the packages it was built from.
+genquery(
+ name = "yamllint_lib_by_version",
+ expression = """
+ attr("tags", "\\bpypi_version=1.26.3\\b", "@pip_yamllint//:pkg")
+ intersect
+ attr("tags", "\\bpypi_name=yamllint\\b", "@pip_yamllint//:pkg")
+ """,
+ scope = [requirement("yamllint")],
+)
+
+write_file(
+ name = "write_expected",
+ out = "expected",
+ content = [
+ "@pip_yamllint//:pkg",
+ "",
+ ],
+)
+
+diff_test(
+ name = "test_query_result",
+ file1 = "expected",
+ file2 = "yamllint_lib_by_version",
+)
diff --git a/examples/pip_install/README.md b/examples/pip_install/README.md
new file mode 100644
index 0000000..7657787
--- /dev/null
+++ b/examples/pip_install/README.md
@@ -0,0 +1,4 @@
+# pip_install example
+
+This example shows how to use pip to fetch external dependencies from a requirements.txt file,
+then use them in BUILD files as dependencies of Bazel targets.
diff --git a/examples/pip_install/WORKSPACE b/examples/pip_install/WORKSPACE
new file mode 100644
index 0000000..b1744bf
--- /dev/null
+++ b/examples/pip_install/WORKSPACE
@@ -0,0 +1,96 @@
+workspace(name = "rules_python_pip_install_example")
+
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+load("@python39//:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "pip_install")
+
+pip_install(
+ # (Optional) You can provide extra parameters to pip.
+ # Here, make pip output verbose (this is usable with `quiet = False`).
+ #extra_pip_args = ["-v"],
+
+ # (Optional) You can exclude custom elements in the data section of the generated BUILD files for pip packages.
+ # Exclude directories with spaces in their names in this example (avoids build errors if there are such directories).
+ #pip_data_exclude = ["**/* */**"],
+
+ # (Optional) You can provide a python_interpreter (path) or a python_interpreter_target (a Bazel target, that
+ # acts as an executable). The latter can be anything that could be used as Python interpreter. E.g.:
+ # 1. Python interpreter that you compile in the build file (as above in @python_interpreter).
+ # 2. Pre-compiled python interpreter included with http_archive
+ # 3. Wrapper script, like in the autodetecting python toolchain.
+ #
+ # Here, we use the interpreter constant that resolves to the host interpreter from the default Python toolchain.
+ python_interpreter_target = interpreter,
+
+ # (Optional) You can set quiet to False if you want to see pip output.
+ #quiet = False,
+
+ # (Optional) You can set an environment in the pip process to control its
+ # behavior. Note that pip is run in "isolated" mode so no PIP_<VAR>_<NAME>
+ # style env vars are read, but env vars that control requests and urllib3
+ # can be passed.
+ #environment = {"HTTP_PROXY": "http://my.proxy.fun/"},
+
+ # Uses the default repository name "pip"
+ requirements = "//:requirements.txt",
+)
+
+load("@pip//:requirements.bzl", "install_deps")
+
+# Initialize repositories for all packages in requirements.txt.
+install_deps()
+
+# You could optionally use an in-build, compiled python interpreter as a toolchain,
+# and also use it to execute pip.
+#
+# Special logic for building python interpreter with OpenSSL from homebrew.
+# See https://devguide.python.org/setup/#macos-and-os-x
+#_py_configure = """
+#if [[ "$OSTYPE" == "darwin"* ]]; then
+# ./configure --prefix=$(pwd)/bazel_install --with-openssl=$(brew --prefix openssl)
+#else
+# ./configure --prefix=$(pwd)/bazel_install
+#fi
+#"""
+#
+# NOTE: you need to have the SSL headers installed to build with openssl support (and use HTTPS).
+# E.g. on Ubuntu: `sudo apt install libssl-dev`
+#http_archive(
+# name = "python_interpreter",
+# build_file_content = """
+#exports_files(["python_bin"])
+#filegroup(
+# name = "files",
+# srcs = glob(["bazel_install/**"], exclude = ["**/* *"]),
+# visibility = ["//visibility:public"],
+#)
+#""",
+# patch_cmds = [
+# "mkdir $(pwd)/bazel_install",
+# _py_configure,
+# "make",
+# "make install",
+# "ln -s bazel_install/bin/python3 python_bin",
+# ],
+# sha256 = "dfab5ec723c218082fe3d5d7ae17ecbdebffa9a1aea4d64aa3a2ecdd2e795864",
+# strip_prefix = "Python-3.8.3",
+# urls = ["https://www.python.org/ftp/python/3.8.3/Python-3.8.3.tar.xz"],
+#)
+
+# Optional:
+# Register the toolchain with the same python interpreter we used for pip in pip_install().
+#register_toolchains("//:my_py_toolchain")
+# End of in-build Python interpreter setup.
diff --git a/examples/pip_install/main.py b/examples/pip_install/main.py
new file mode 100644
index 0000000..1fb7249
--- /dev/null
+++ b/examples/pip_install/main.py
@@ -0,0 +1,23 @@
+# 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.
+
+import boto3
+
+
+def the_dir():
+ return dir(boto3)
+
+
+if __name__ == "__main__":
+ print(the_dir())
diff --git a/examples/pip_install/pip_install_test.py b/examples/pip_install/pip_install_test.py
new file mode 100644
index 0000000..3e1b085
--- /dev/null
+++ b/examples/pip_install/pip_install_test.py
@@ -0,0 +1,80 @@
+#!/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.
+
+
+import os
+import subprocess
+import unittest
+from pathlib import Path
+
+from rules_python.python.runfiles import runfiles
+
+
+class PipInstallTest(unittest.TestCase):
+ maxDiff = None
+
+ def test_entry_point(self):
+ env = os.environ.get("YAMLLINT_ENTRY_POINT")
+ self.assertIsNotNone(env)
+
+ r = runfiles.Create()
+
+ # To find an external target, this must use `{workspace_name}/$(rootpath @external_repo//:target)`
+ entry_point = Path(
+ r.Rlocation("rules_python_pip_install_example/{}".format(env))
+ )
+ self.assertTrue(entry_point.exists())
+
+ proc = subprocess.run(
+ [str(entry_point), "--version"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.26.3")
+
+ def test_data(self):
+ env = os.environ.get("WHEEL_DATA_CONTENTS")
+ self.assertIsNotNone(env)
+ self.assertListEqual(
+ env.split(" "),
+ [
+ "external/pip_s3cmd/data/share/doc/packages/s3cmd/INSTALL.md",
+ "external/pip_s3cmd/data/share/doc/packages/s3cmd/LICENSE",
+ "external/pip_s3cmd/data/share/doc/packages/s3cmd/NEWS",
+ "external/pip_s3cmd/data/share/doc/packages/s3cmd/README.md",
+ "external/pip_s3cmd/data/share/man/man1/s3cmd.1",
+ ],
+ )
+
+ def test_dist_info(self):
+ env = os.environ.get("WHEEL_DIST_INFO_CONTENTS")
+ self.assertIsNotNone(env)
+ self.assertListEqual(
+ env.split(" "),
+ [
+ "external/pip_boto3/site-packages/boto3-1.14.63.dist-info/DESCRIPTION.rst",
+ "external/pip_boto3/site-packages/boto3-1.14.63.dist-info/INSTALLER",
+ "external/pip_boto3/site-packages/boto3-1.14.63.dist-info/METADATA",
+ "external/pip_boto3/site-packages/boto3-1.14.63.dist-info/RECORD",
+ "external/pip_boto3/site-packages/boto3-1.14.63.dist-info/WHEEL",
+ "external/pip_boto3/site-packages/boto3-1.14.63.dist-info/metadata.json",
+ "external/pip_boto3/site-packages/boto3-1.14.63.dist-info/top_level.txt",
+ ],
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/pip_install/requirements.in b/examples/pip_install/requirements.in
new file mode 100644
index 0000000..11ede3c
--- /dev/null
+++ b/examples/pip_install/requirements.in
@@ -0,0 +1,4 @@
+boto3~=1.14.51
+s3cmd~=2.1.0
+yamllint~=1.26.3
+tree-sitter==0.20.0 ; sys_platform != "win32"
diff --git a/examples/pip_install/requirements.txt b/examples/pip_install/requirements.txt
new file mode 100644
index 0000000..495a32a
--- /dev/null
+++ b/examples/pip_install/requirements.txt
@@ -0,0 +1,110 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+boto3==1.14.63 \
+ --hash=sha256:25c716b7c01d4664027afc6a6418a06459e311a610c7fd39a030a1ced1b72ce4 \
+ --hash=sha256:37158c37a151eab5b9080968305621a40168171fda9584d50a309ceb4e5e6964
+ # via -r requirements.in
+botocore==1.17.63 \
+ --hash=sha256:40f13f6c9c29c307a9dc5982739e537ddce55b29787b90c3447b507e3283bcd6 \
+ --hash=sha256:aa88eafc6295132f4bc606f1df32b3248e0fa611724c0a216aceda767948ac75
+ # via
+ # boto3
+ # s3transfer
+docutils==0.15.2 \
+ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \
+ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \
+ --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99
+ # via botocore
+jmespath==0.10.0 \
+ --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \
+ --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f
+ # via
+ # boto3
+ # botocore
+pathspec==0.10.3 \
+ --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \
+ --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6
+ # via yamllint
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # botocore
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+s3transfer==0.3.7 \
+ --hash=sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994 \
+ --hash=sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246
+ # via boto3
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+tree-sitter==0.20.0 ; sys_platform != "win32" \
+ --hash=sha256:1940f64be1e8c9c3c0e34a2258f1e4c324207534d5b1eefc5ab2960a9d98f668 \
+ --hash=sha256:51a609a7c1bd9d9e75d92ee128c12c7852ae70a482900fbbccf3d13a79e0378c
+ # via -r requirements.in
+urllib3==1.25.11 \
+ --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \
+ --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e
+ # via botocore
+yamllint==1.26.3 \
+ --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e
+ # via -r requirements.in
diff --git a/examples/pip_install/requirements_windows.txt b/examples/pip_install/requirements_windows.txt
new file mode 100644
index 0000000..b87192f
--- /dev/null
+++ b/examples/pip_install/requirements_windows.txt
@@ -0,0 +1,106 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+boto3==1.14.63 \
+ --hash=sha256:25c716b7c01d4664027afc6a6418a06459e311a610c7fd39a030a1ced1b72ce4 \
+ --hash=sha256:37158c37a151eab5b9080968305621a40168171fda9584d50a309ceb4e5e6964
+ # via -r requirements.in
+botocore==1.17.63 \
+ --hash=sha256:40f13f6c9c29c307a9dc5982739e537ddce55b29787b90c3447b507e3283bcd6 \
+ --hash=sha256:aa88eafc6295132f4bc606f1df32b3248e0fa611724c0a216aceda767948ac75
+ # via
+ # boto3
+ # s3transfer
+docutils==0.15.2 \
+ --hash=sha256:6c4f696463b79f1fb8ba0c594b63840ebd41f059e92b31957c46b74a4599b6d0 \
+ --hash=sha256:9e4d7ecfc600058e07ba661411a2b7de2fd0fafa17d1a7f7361cd47b1175c827 \
+ --hash=sha256:a2aeea129088da402665e92e0b25b04b073c04b2dce4ab65caaa38b7ce2e1a99
+ # via botocore
+jmespath==0.10.0 \
+ --hash=sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9 \
+ --hash=sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f
+ # via
+ # boto3
+ # botocore
+pathspec==0.10.3 \
+ --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \
+ --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6
+ # via yamllint
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via
+ # botocore
+ # s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+s3transfer==0.3.7 \
+ --hash=sha256:35627b86af8ff97e7ac27975fe0a98a312814b46c6333d8a6b889627bcd80994 \
+ --hash=sha256:efa5bd92a897b6a8d5c1383828dca3d52d0790e0756d49740563a3fb6ed03246
+ # via boto3
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+urllib3==1.25.11 \
+ --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \
+ --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e
+ # via botocore
+yamllint==1.26.3 \
+ --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e
+ # via -r requirements.in
diff --git a/examples/pip_install/test.py b/examples/pip_install/test.py
new file mode 100644
index 0000000..0f5b7c9
--- /dev/null
+++ b/examples/pip_install/test.py
@@ -0,0 +1,26 @@
+# 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.
+
+import unittest
+
+import main
+
+
+class ExampleTest(unittest.TestCase):
+ def test_main(self):
+ self.assertIn("set_stream_logger", main.the_dir())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/pip_parse/.bazelrc b/examples/pip_parse/.bazelrc
new file mode 100644
index 0000000..9e7ef37
--- /dev/null
+++ b/examples/pip_parse/.bazelrc
@@ -0,0 +1,2 @@
+# https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file
+try-import %workspace%/user.bazelrc
diff --git a/examples/pip_parse/.gitignore b/examples/pip_parse/.gitignore
new file mode 100644
index 0000000..e5ae073
--- /dev/null
+++ b/examples/pip_parse/.gitignore
@@ -0,0 +1,4 @@
+# git ignore patterns
+
+/bazel-*
+user.bazelrc
diff --git a/examples/pip_parse/BUILD.bazel b/examples/pip_parse/BUILD.bazel
new file mode 100644
index 0000000..653f75c
--- /dev/null
+++ b/examples/pip_parse/BUILD.bazel
@@ -0,0 +1,81 @@
+load(
+ "@pypi//:requirements.bzl",
+ "data_requirement",
+ "dist_info_requirement",
+ "entry_point",
+)
+load("@rules_python//python:defs.bzl", "py_binary", "py_test")
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+
+# Toolchain setup, this is optional.
+# Demonstrate that we can use the same python interpreter for the toolchain and executing pip in pip install (see WORKSPACE).
+#
+#load("@rules_python//python:defs.bzl", "py_runtime_pair")
+#
+#py_runtime(
+# name = "python3_runtime",
+# files = ["@python_interpreter//:files"],
+# interpreter = "@python_interpreter//:python_bin",
+# python_version = "PY3",
+# visibility = ["//visibility:public"],
+#)
+#
+#py_runtime_pair(
+# name = "my_py_runtime_pair",
+# py2_runtime = None,
+# py3_runtime = ":python3_runtime",
+#)
+#
+#toolchain(
+# name = "my_py_toolchain",
+# toolchain = ":my_py_runtime_pair",
+# toolchain_type = "@bazel_tools//tools/python:toolchain_type",
+#)
+# End of toolchain setup.
+
+py_binary(
+ name = "main",
+ srcs = ["main.py"],
+ deps = [
+ "@pypi_requests//:pkg",
+ ],
+)
+
+py_test(
+ name = "test",
+ srcs = ["test.py"],
+ deps = [":main"],
+)
+
+# For pip dependencies which have entry points, the `entry_point` macro can be
+# used from the generated `pip_parse` repository to access a runnable binary.
+
+alias(
+ name = "yamllint",
+ actual = entry_point("yamllint"),
+)
+
+# This rule adds a convenient way to update the requirements file.
+compile_pip_requirements(
+ name = "requirements",
+ extra_args = ["--allow-unsafe"],
+ requirements_in = "requirements.in",
+ requirements_txt = "requirements_lock.txt",
+)
+
+# Test the use of all pip_parse utilities in a single py_test
+py_test(
+ name = "pip_parse_test",
+ srcs = ["pip_parse_test.py"],
+ data = [
+ ":yamllint",
+ data_requirement("s3cmd"),
+ dist_info_requirement("requests"),
+ ],
+ env = {
+ "WHEEL_DATA_CONTENTS": "$(rootpaths {})".format(data_requirement("s3cmd")),
+ "WHEEL_DIST_INFO_CONTENTS": "$(rootpaths {})".format(dist_info_requirement("requests")),
+ "YAMLLINT_ENTRY_POINT": "$(rootpath :yamllint)",
+ },
+ deps = ["@rules_python//python/runfiles"],
+)
diff --git a/examples/pip_parse/WORKSPACE b/examples/pip_parse/WORKSPACE
new file mode 100644
index 0000000..79aca14
--- /dev/null
+++ b/examples/pip_parse/WORKSPACE
@@ -0,0 +1,52 @@
+workspace(name = "rules_python_pip_parse_example")
+
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+load("@python39//:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+pip_parse(
+ # (Optional) You can set an environment in the pip process to control its
+ # behavior. Note that pip is run in "isolated" mode so no PIP_<VAR>_<NAME>
+ # style env vars are read, but env vars that control requests and urllib3
+ # can be passed
+ # environment = {"HTTPS_PROXY": "http://my.proxy.fun/"},
+ name = "pypi",
+ # (Optional) You can provide extra parameters to pip.
+ # Here, make pip output verbose (this is usable with `quiet = False`).
+ # extra_pip_args = ["-v"],
+
+ # (Optional) You can exclude custom elements in the data section of the generated BUILD files for pip packages.
+ # Exclude directories with spaces in their names in this example (avoids build errors if there are such directories).
+ #pip_data_exclude = ["**/* */**"],
+
+ # (Optional) You can provide a python_interpreter (path) or a python_interpreter_target (a Bazel target, that
+ # acts as an executable). The latter can be anything that could be used as Python interpreter. E.g.:
+ # 1. Python interpreter that you compile in the build file (as above in @python_interpreter).
+ # 2. Pre-compiled python interpreter included with http_archive
+ # 3. Wrapper script, like in the autodetecting python toolchain.
+ #
+ # Here, we use the interpreter constant that resolves to the host interpreter from the default Python toolchain.
+ python_interpreter_target = interpreter,
+
+ # (Optional) You can set quiet to False if you want to see pip output.
+ #quiet = False,
+ requirements_lock = "//:requirements_lock.txt",
+)
+
+load("@pypi//:requirements.bzl", "install_deps")
+
+# Initialize repositories for all packages in requirements_lock.txt.
+install_deps()
diff --git a/examples/pip_parse/main.py b/examples/pip_parse/main.py
new file mode 100644
index 0000000..80610f4
--- /dev/null
+++ b/examples/pip_parse/main.py
@@ -0,0 +1,19 @@
+# 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.
+
+import requests
+
+
+def version():
+ return requests.__version__
diff --git a/examples/pip_parse/pip_parse_test.py b/examples/pip_parse/pip_parse_test.py
new file mode 100644
index 0000000..f319cb8
--- /dev/null
+++ b/examples/pip_parse/pip_parse_test.py
@@ -0,0 +1,77 @@
+#!/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.
+
+
+import os
+import subprocess
+import unittest
+from pathlib import Path
+
+from rules_python.python.runfiles import runfiles
+
+
+class PipInstallTest(unittest.TestCase):
+ maxDiff = None
+
+ def test_entry_point(self):
+ env = os.environ.get("YAMLLINT_ENTRY_POINT")
+ self.assertIsNotNone(env)
+
+ r = runfiles.Create()
+
+ # To find an external target, this must use `{workspace_name}/$(rootpath @external_repo//:target)`
+ entry_point = Path(r.Rlocation("rules_python_pip_parse_example/{}".format(env)))
+ self.assertTrue(entry_point.exists())
+
+ proc = subprocess.run(
+ [str(entry_point), "--version"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.26.3")
+
+ def test_data(self):
+ env = os.environ.get("WHEEL_DATA_CONTENTS")
+ self.assertIsNotNone(env)
+ self.assertListEqual(
+ env.split(" "),
+ [
+ "external/pypi_s3cmd/data/share/doc/packages/s3cmd/INSTALL.md",
+ "external/pypi_s3cmd/data/share/doc/packages/s3cmd/LICENSE",
+ "external/pypi_s3cmd/data/share/doc/packages/s3cmd/NEWS",
+ "external/pypi_s3cmd/data/share/doc/packages/s3cmd/README.md",
+ "external/pypi_s3cmd/data/share/man/man1/s3cmd.1",
+ ],
+ )
+
+ def test_dist_info(self):
+ env = os.environ.get("WHEEL_DIST_INFO_CONTENTS")
+ self.assertIsNotNone(env)
+ self.assertListEqual(
+ env.split(" "),
+ [
+ "external/pypi_requests/site-packages/requests-2.25.1.dist-info/INSTALLER",
+ "external/pypi_requests/site-packages/requests-2.25.1.dist-info/LICENSE",
+ "external/pypi_requests/site-packages/requests-2.25.1.dist-info/METADATA",
+ "external/pypi_requests/site-packages/requests-2.25.1.dist-info/RECORD",
+ "external/pypi_requests/site-packages/requests-2.25.1.dist-info/WHEEL",
+ "external/pypi_requests/site-packages/requests-2.25.1.dist-info/top_level.txt",
+ ],
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/pip_parse/requirements.in b/examples/pip_parse/requirements.in
new file mode 100644
index 0000000..ec2102f
--- /dev/null
+++ b/examples/pip_parse/requirements.in
@@ -0,0 +1,3 @@
+requests~=2.25.1
+s3cmd~=2.1.0
+yamllint~=1.26.3
diff --git a/examples/pip_parse/requirements_lock.txt b/examples/pip_parse/requirements_lock.txt
new file mode 100644
index 0000000..3cbe57f
--- /dev/null
+++ b/examples/pip_parse/requirements_lock.txt
@@ -0,0 +1,95 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+pathspec==0.10.3 \
+ --hash=sha256:3c95343af8b756205e2aba76e843ba9520a24dd84f68c22b9f93251507509dd6 \
+ --hash=sha256:56200de4077d9d0791465aa9095a01d421861e405b5096955051deefd697d6f6
+ # via yamllint
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via s3cmd
+python-magic==0.4.27 \
+ --hash=sha256:c1ba14b08e4a5f5c31a302b7721239695b2f0f058d125bd5ce1ee36b9d9d3c3b \
+ --hash=sha256:c212960ad306f700aa0d01e5d7a325d20548ff97eb9920dcd29513174f0294d3
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:01b45c0191e6d66c470b6cf1b9531a771a83c1c4208272ead47a3ae4f2f603bf \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:432557aa2c09802be39460360ddffd48156e30721f5e8d917f01d31694216782 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:81957921f441d50af23654aa6c5e5eaf9b06aba7f0a19c18a538dc7ef291c5a1 \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:afa17f5bc4d1b10afd4466fd3a44dc0e245382deca5b3c353d8b757f9e3ecb8d \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:bfaef573a63ba8923503d27530362590ff4f576c626d86a9fed95822a8255fd7 \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4b0ba9512519522b118090257be113b9468d804b19d63c71dbcf4a48fa32358 \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:dbad0e9d368bb989f4515da330b88a057617d16b6a8245084f1b05400f24609f \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
+ # via requests
+yamllint==1.26.3 \
+ --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e
+ # via -r requirements.in
diff --git a/examples/pip_parse/test.py b/examples/pip_parse/test.py
new file mode 100644
index 0000000..2dc3046
--- /dev/null
+++ b/examples/pip_parse/test.py
@@ -0,0 +1,26 @@
+# 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.
+
+import unittest
+
+import main
+
+
+class ExampleTest(unittest.TestCase):
+ def test_main(self):
+ self.assertEqual("2.25.1", main.version())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/pip_parse_vendored/.bazelrc b/examples/pip_parse_vendored/.bazelrc
new file mode 100644
index 0000000..f23315a
--- /dev/null
+++ b/examples/pip_parse_vendored/.bazelrc
@@ -0,0 +1,5 @@
+test --test_output=errors
+
+# Windows requires these for multi-python support:
+build --enable_runfiles
+startup --windows_enable_symlinks
diff --git a/examples/pip_parse_vendored/.gitignore b/examples/pip_parse_vendored/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/examples/pip_parse_vendored/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/examples/pip_parse_vendored/BUILD.bazel b/examples/pip_parse_vendored/BUILD.bazel
new file mode 100644
index 0000000..56630e5
--- /dev/null
+++ b/examples/pip_parse_vendored/BUILD.bazel
@@ -0,0 +1,52 @@
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+
+# This rule adds a convenient way to update the requirements.txt
+# lockfile based on the requirements.in.
+compile_pip_requirements(name = "requirements")
+
+# The requirements.bzl file is generated with a reference to the interpreter for the host platform.
+# In order to check in a platform-agnostic file, we have to replace that reference with the symbol
+# loaded from our python toolchain.
+genrule(
+ name = "make_platform_agnostic",
+ srcs = ["@pip//:requirements.bzl"],
+ outs = ["requirements.clean.bzl"],
+ cmd = " | ".join([
+ "cat $<",
+ # Insert our load statement after the existing one so we don't produce a file with buildifier warnings
+ """sed -e '/^load.*/i\\'$$'\\n''load("@python39//:defs.bzl", "interpreter")'""",
+ # Replace the bazel 6.0.0 specific comment with something that bazel 5.4.0 would produce.
+ # This enables this example to be run as a test under bazel 5.4.0.
+ """sed -e 's#@//#//#'""",
+ """sed 's#"@python39_.*//:bin/python3"#interpreter#' >$@""",
+ ]),
+)
+
+write_file(
+ name = "gen_update",
+ out = "update.sh",
+ content = [
+ # This depends on bash, would need tweaks for Windows
+ "#!/usr/bin/env bash",
+ # Bazel gives us a way to access the source folder!
+ "cd $BUILD_WORKSPACE_DIRECTORY",
+ "cp -fv bazel-bin/requirements.clean.bzl requirements.bzl",
+ ],
+)
+
+sh_binary(
+ name = "vendor_requirements",
+ srcs = ["update.sh"],
+ data = [":make_platform_agnostic"],
+)
+
+# Similarly ensures that the requirements.bzl file is updated
+# based on the requirements.txt lockfile.
+diff_test(
+ name = "test_vendored",
+ failure_message = "Please run: bazel run //:vendor_requirements",
+ file1 = "requirements.bzl",
+ file2 = ":make_platform_agnostic",
+)
diff --git a/examples/pip_parse_vendored/README.md b/examples/pip_parse_vendored/README.md
new file mode 100644
index 0000000..f53260a
--- /dev/null
+++ b/examples/pip_parse_vendored/README.md
@@ -0,0 +1,31 @@
+# pip_parse vendored
+
+This example is like pip_parse, however we avoid loading from the generated file.
+See https://github.com/bazelbuild/rules_python/issues/608
+and https://blog.aspect.dev/avoid-eager-fetches.
+
+The requirements now form a triple:
+
+- requirements.in - human editable, expresses only direct dependencies and load-bearing version constraints
+- requirements.txt - lockfile produced by pip-compile or other means
+- requirements.bzl - the "parsed" version of the lockfile readable by Bazel downloader
+
+The `requirements.bzl` file contains baked-in attributes such as `python_interpreter_target` as they were specified in the original `pip_parse` rule. These can be overridden at install time by passing arguments to `install_deps`. For example:
+
+```python
+# Register a hermetic toolchain
+load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+load("@python39//:defs.bzl", "interpreter")
+
+# Load dependencies vendored by some other ruleset.
+load("@some_rules//:py_deps.bzl", "install_deps")
+
+install_deps(
+ python_interpreter_target = interpreter,
+)
+```
diff --git a/examples/pip_parse_vendored/WORKSPACE b/examples/pip_parse_vendored/WORKSPACE
new file mode 100644
index 0000000..157f70a
--- /dev/null
+++ b/examples/pip_parse_vendored/WORKSPACE
@@ -0,0 +1,35 @@
+workspace(name = "pip_repository_annotations_example")
+
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+load("@python39//:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+# This repository isn't referenced, except by our test that asserts the requirements.bzl is updated.
+# It also wouldn't be needed by users of this ruleset.
+pip_parse(
+ name = "pip",
+ python_interpreter_target = interpreter,
+ requirements_lock = "//:requirements.txt",
+)
+
+# This example vendors the file produced by `pip_parse` above into the repo.
+# This way our Bazel doesn't eagerly fetch and install the pip_parse'd
+# repository for builds that don't need it.
+# See discussion of the trade-offs in the pip_parse documentation
+# and the "vendor_requirements" target in the BUILD file.
+load("//:requirements.bzl", "install_deps")
+
+install_deps()
diff --git a/examples/pip_parse_vendored/requirements.bzl b/examples/pip_parse_vendored/requirements.bzl
new file mode 100644
index 0000000..7bf5170
--- /dev/null
+++ b/examples/pip_parse_vendored/requirements.bzl
@@ -0,0 +1,55 @@
+"""Starlark representation of locked requirements.
+
+@generated by rules_python pip_parse repository rule
+from //:requirements.txt
+"""
+
+load("@python39//:defs.bzl", "interpreter")
+load("@rules_python//python/pip_install:pip_repository.bzl", "whl_library")
+
+all_requirements = ["@pip_certifi//:pkg", "@pip_charset_normalizer//:pkg", "@pip_idna//:pkg", "@pip_requests//:pkg", "@pip_urllib3//:pkg"]
+
+all_whl_requirements = ["@pip_certifi//:whl", "@pip_charset_normalizer//:whl", "@pip_idna//:whl", "@pip_requests//:whl", "@pip_urllib3//:whl"]
+
+all_data_requirements = ["@pip_certifi//:data", "@pip_charset_normalizer//:data", "@pip_idna//:data", "@pip_requests//:data", "@pip_urllib3//:data"]
+
+_packages = [("pip_certifi", "certifi==2022.12.7 --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"), ("pip_charset_normalizer", "charset-normalizer==2.1.1 --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f"), ("pip_idna", "idna==3.4 --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"), ("pip_requests", "requests==2.28.1 --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349"), ("pip_urllib3", "urllib3==1.26.13 --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8")]
+_config = {"download_only": False, "enable_implicit_namespace_pkgs": False, "environment": {}, "extra_pip_args": [], "isolated": True, "pip_data_exclude": [], "python_interpreter": "python3", "python_interpreter_target": interpreter, "quiet": True, "repo": "pip", "repo_prefix": "pip_", "timeout": 600}
+_annotations = {}
+
+def _clean_name(name):
+ return name.replace("-", "_").replace(".", "_").lower()
+
+def requirement(name):
+ return "@pip_" + _clean_name(name) + "//:pkg"
+
+def whl_requirement(name):
+ return "@pip_" + _clean_name(name) + "//:whl"
+
+def data_requirement(name):
+ return "@pip_" + _clean_name(name) + "//:data"
+
+def dist_info_requirement(name):
+ return "@pip_" + _clean_name(name) + "//:dist_info"
+
+def entry_point(pkg, script = None):
+ if not script:
+ script = pkg
+ return "@pip_" + _clean_name(pkg) + "//:rules_python_wheel_entry_point_" + script
+
+def _get_annotation(requirement):
+ # This expects to parse `setuptools==58.2.0 --hash=sha256:2551203ae6955b9876741a26ab3e767bb3242dafe86a32a749ea0d78b6792f11`
+ # down to `setuptools`.
+ name = requirement.split(" ")[0].split("=")[0].split("[")[0]
+ return _annotations.get(name)
+
+def install_deps(**whl_library_kwargs):
+ whl_config = dict(_config)
+ whl_config.update(whl_library_kwargs)
+ for name, requirement in _packages:
+ whl_library(
+ name = name,
+ requirement = requirement,
+ annotation = _get_annotation(requirement),
+ **whl_config
+ )
diff --git a/examples/pip_parse_vendored/requirements.in b/examples/pip_parse_vendored/requirements.in
new file mode 100644
index 0000000..f229360
--- /dev/null
+++ b/examples/pip_parse_vendored/requirements.in
@@ -0,0 +1 @@
+requests
diff --git a/examples/pip_parse_vendored/requirements.txt b/examples/pip_parse_vendored/requirements.txt
new file mode 100644
index 0000000..ff1a363
--- /dev/null
+++ b/examples/pip_parse_vendored/requirements.txt
@@ -0,0 +1,26 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+charset-normalizer==2.1.1 \
+ --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \
+ --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f
+ # via requests
+idna==3.4 \
+ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+ # via requests
+requests==2.28.1 \
+ --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
+ --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
+ # via -r requirements.in
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
+ # via requests
diff --git a/examples/pip_repository_annotations/.bazelrc b/examples/pip_repository_annotations/.bazelrc
new file mode 100644
index 0000000..9e7ef37
--- /dev/null
+++ b/examples/pip_repository_annotations/.bazelrc
@@ -0,0 +1,2 @@
+# https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file
+try-import %workspace%/user.bazelrc
diff --git a/examples/pip_repository_annotations/.gitignore b/examples/pip_repository_annotations/.gitignore
new file mode 100644
index 0000000..a6ef824
--- /dev/null
+++ b/examples/pip_repository_annotations/.gitignore
@@ -0,0 +1 @@
+/bazel-*
diff --git a/examples/pip_repository_annotations/BUILD.bazel b/examples/pip_repository_annotations/BUILD.bazel
new file mode 100644
index 0000000..84089f7
--- /dev/null
+++ b/examples/pip_repository_annotations/BUILD.bazel
@@ -0,0 +1,44 @@
+load("@pip_installed//:requirements.bzl", "requirement")
+load("@rules_python//python:defs.bzl", "py_test")
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+
+exports_files(
+ glob(["data/**"]),
+ visibility = ["//visibility:public"],
+)
+
+# This rule adds a convenient way to update the requirements file.
+compile_pip_requirements(
+ name = "requirements",
+ extra_args = ["--allow-unsafe"],
+)
+
+py_test(
+ name = "pip_parse_annotations_test",
+ srcs = ["pip_repository_annotations_test.py"],
+ env = {
+ "REQUESTS_PKG_DIR": "pip_parsed_requests",
+ "WHEEL_PKG_DIR": "pip_parsed_wheel",
+ },
+ main = "pip_repository_annotations_test.py",
+ deps = [
+ "@pip_parsed_requests//:pkg",
+ "@pip_parsed_wheel//:pkg",
+ "@rules_python//python/runfiles",
+ ],
+)
+
+py_test(
+ name = "pip_install_annotations_test",
+ srcs = ["pip_repository_annotations_test.py"],
+ env = {
+ "REQUESTS_PKG_DIR": "pip_installed_requests",
+ "WHEEL_PKG_DIR": "pip_installed_wheel",
+ },
+ main = "pip_repository_annotations_test.py",
+ deps = [
+ requirement("wheel"),
+ requirement("requests"),
+ "@rules_python//python/runfiles",
+ ],
+)
diff --git a/examples/pip_repository_annotations/WORKSPACE b/examples/pip_repository_annotations/WORKSPACE
new file mode 100644
index 0000000..3deea03
--- /dev/null
+++ b/examples/pip_repository_annotations/WORKSPACE
@@ -0,0 +1,75 @@
+workspace(name = "pip_repository_annotations_example")
+
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+load("@python39//:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "package_annotation", "pip_install", "pip_parse")
+
+# Here we can see an example of annotations being applied to an arbitrary
+# package. For details on `package_annotation` and it's uses, see the
+# docs at @rules_python//docs:pip.md`.
+ANNOTATIONS = {
+ # This annotation verifies that annotations work correctly for pip packages with extras
+ # specified, in this case requests[security].
+ "requests": package_annotation(
+ additive_build_content = """\
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+write_file(
+ name = "generated_file",
+ out = "generated_file.txt",
+ content = ["Hello world from requests"],
+)
+""",
+ data = [":generated_file"],
+ ),
+ "wheel": package_annotation(
+ additive_build_content = """\
+load("@bazel_skylib//rules:write_file.bzl", "write_file")
+write_file(
+ name = "generated_file",
+ out = "generated_file.txt",
+ content = ["Hello world from build content file"],
+)
+""",
+ copy_executables = {"@pip_repository_annotations_example//:data/copy_executable.py": "copied_content/executable.py"},
+ copy_files = {"@pip_repository_annotations_example//:data/copy_file.txt": "copied_content/file.txt"},
+ data = [":generated_file"],
+ data_exclude_glob = ["site-packages/*.dist-info/WHEEL"],
+ ),
+}
+
+# For a more thorough example of `pip_parse`. See `@rules_python//examples/pip_parse`
+pip_parse(
+ name = "pip_parsed",
+ annotations = ANNOTATIONS,
+ python_interpreter_target = interpreter,
+ requirements_lock = "//:requirements.txt",
+)
+
+load("@pip_parsed//:requirements.bzl", install_pip_parse_deps = "install_deps")
+
+install_pip_parse_deps()
+
+# For a more thorough example of `pip_install`. See `@rules_python//examples/pip_install`
+pip_install(
+ name = "pip_installed",
+ annotations = ANNOTATIONS,
+ python_interpreter_target = interpreter,
+ requirements = "//:requirements.txt",
+)
+
+load("@pip_installed//:requirements.bzl", install_pip_install_deps = "install_deps")
+
+install_pip_install_deps()
diff --git a/examples/pip_repository_annotations/data/copy_executable.py b/examples/pip_repository_annotations/data/copy_executable.py
new file mode 100755
index 0000000..5cb1af7
--- /dev/null
+++ b/examples/pip_repository_annotations/data/copy_executable.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python
+# 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.
+
+
+if __name__ == "__main__":
+ print("Hello world from copied executable")
diff --git a/examples/pip_repository_annotations/data/copy_file.txt b/examples/pip_repository_annotations/data/copy_file.txt
new file mode 100644
index 0000000..b1020f7
--- /dev/null
+++ b/examples/pip_repository_annotations/data/copy_file.txt
@@ -0,0 +1 @@
+Hello world from copied file
diff --git a/examples/pip_repository_annotations/pip_repository_annotations_test.py b/examples/pip_repository_annotations/pip_repository_annotations_test.py
new file mode 100644
index 0000000..e41dd4f
--- /dev/null
+++ b/examples/pip_repository_annotations/pip_repository_annotations_test.py
@@ -0,0 +1,129 @@
+#!/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.
+
+
+import os
+import platform
+import subprocess
+import sys
+import unittest
+from pathlib import Path
+
+from rules_python.python.runfiles import runfiles
+
+
+class PipRepositoryAnnotationsTest(unittest.TestCase):
+ maxDiff = None
+
+ def wheel_pkg_dir(self) -> str:
+ env = os.environ.get("WHEEL_PKG_DIR")
+ self.assertIsNotNone(env)
+ return env
+
+ def test_build_content_and_data(self):
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "pip_repository_annotations_example/external/{}/generated_file.txt".format(
+ self.wheel_pkg_dir()
+ )
+ )
+ generated_file = Path(rpath)
+ self.assertTrue(generated_file.exists())
+
+ content = generated_file.read_text().rstrip()
+ self.assertEqual(content, "Hello world from build content file")
+
+ def test_copy_files(self):
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "pip_repository_annotations_example/external/{}/copied_content/file.txt".format(
+ self.wheel_pkg_dir()
+ )
+ )
+ copied_file = Path(rpath)
+ self.assertTrue(copied_file.exists())
+
+ content = copied_file.read_text().rstrip()
+ self.assertEqual(content, "Hello world from copied file")
+
+ def test_copy_executables(self):
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "pip_repository_annotations_example/external/{}/copied_content/executable{}".format(
+ self.wheel_pkg_dir(),
+ ".exe" if platform.system() == "windows" else ".py",
+ )
+ )
+ executable = Path(rpath)
+ self.assertTrue(executable.exists())
+
+ proc = subprocess.run(
+ [sys.executable, str(executable)],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ stdout = proc.stdout.decode("utf-8").strip()
+ self.assertEqual(stdout, "Hello world from copied executable")
+
+ def test_data_exclude_glob(self):
+ current_wheel_version = "0.38.4"
+
+ r = runfiles.Create()
+ dist_info_dir = "pip_repository_annotations_example/external/{}/site-packages/wheel-{}.dist-info".format(
+ self.wheel_pkg_dir(),
+ current_wheel_version,
+ )
+
+ # Note: `METADATA` is important as it's consumed by https://docs.python.org/3/library/importlib.metadata.html
+ # `METADATA` is expected to be there to show dist-info files are included in the runfiles.
+ metadata_path = r.Rlocation("{}/METADATA".format(dist_info_dir))
+
+ # However, `WHEEL` was explicitly excluded, so it should be missing
+ wheel_path = r.Rlocation("{}/WHEEL".format(dist_info_dir))
+
+ # Because windows does not have `--enable_runfiles` on by default, the
+ # `runfiles.Rlocation` results will be different on this platform vs
+ # unix platforms. See `@rules_python//python/runfiles` for more details.
+ if platform.system() == "Windows":
+ self.assertIsNotNone(metadata_path)
+ self.assertIsNone(wheel_path)
+ else:
+ self.assertTrue(Path(metadata_path).exists())
+ self.assertFalse(Path(wheel_path).exists())
+
+ def requests_pkg_dir(self) -> str:
+ env = os.environ.get("REQUESTS_PKG_DIR")
+ self.assertIsNotNone(env)
+ return env
+
+ def test_extra(self):
+ # This test verifies that annotations work correctly for pip packages with extras
+ # specified, in this case requests[security].
+ r = runfiles.Create()
+ rpath = r.Rlocation(
+ "pip_repository_annotations_example/external/{}/generated_file.txt".format(
+ self.requests_pkg_dir()
+ )
+ )
+ generated_file = Path(rpath)
+ self.assertTrue(generated_file.exists())
+
+ content = generated_file.read_text().rstrip()
+ self.assertEqual(content, "Hello world from requests")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/pip_repository_annotations/requirements.in b/examples/pip_repository_annotations/requirements.in
new file mode 100644
index 0000000..fd3f75c
--- /dev/null
+++ b/examples/pip_repository_annotations/requirements.in
@@ -0,0 +1,6 @@
+# This flag allows for regression testing requirements arguments in
+# `pip_repository` rules.
+--extra-index-url https://pypi.python.org/simple/
+
+wheel
+requests[security]>=2.8.1
diff --git a/examples/pip_repository_annotations/requirements.txt b/examples/pip_repository_annotations/requirements.txt
new file mode 100644
index 0000000..9fde0a9
--- /dev/null
+++ b/examples/pip_repository_annotations/requirements.txt
@@ -0,0 +1,32 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+--extra-index-url https://pypi.python.org/simple/
+
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+charset-normalizer==2.1.1 \
+ --hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \
+ --hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f
+ # via requests
+idna==3.4 \
+ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+ # via requests
+requests[security]==2.28.1 \
+ --hash=sha256:7c5599b102feddaa661c826c56ab4fee28bfd17f5abca1ebbe3e7f19d7c97983 \
+ --hash=sha256:8fefa2a1a1365bf5520aac41836fbee479da67864514bdb821f31ce07ce65349
+ # via -r requirements.in
+urllib3==1.26.13 \
+ --hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
+ --hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
+ # via requests
+wheel==0.38.4 \
+ --hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \
+ --hash=sha256:b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8
+ # via -r requirements.in
diff --git a/examples/py_proto_library/.bazelrc b/examples/py_proto_library/.bazelrc
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/py_proto_library/.bazelrc
diff --git a/examples/py_proto_library/.gitignore b/examples/py_proto_library/.gitignore
new file mode 100644
index 0000000..e5ae073
--- /dev/null
+++ b/examples/py_proto_library/.gitignore
@@ -0,0 +1,4 @@
+# git ignore patterns
+
+/bazel-*
+user.bazelrc
diff --git a/examples/py_proto_library/BUILD.bazel b/examples/py_proto_library/BUILD.bazel
new file mode 100644
index 0000000..7a18a5e
--- /dev/null
+++ b/examples/py_proto_library/BUILD.bazel
@@ -0,0 +1,22 @@
+load("@rules_proto//proto:defs.bzl", "proto_library")
+load("@rules_python//python:defs.bzl", "py_test")
+load("@rules_python//python:proto.bzl", "py_proto_library")
+
+py_proto_library(
+ name = "pricetag_proto_py_pb2",
+ deps = [":pricetag_proto"],
+)
+
+proto_library(
+ name = "pricetag_proto",
+ srcs = ["pricetag.proto"],
+)
+
+py_test(
+ name = "pricetag_test",
+ srcs = ["test.py"],
+ main = "test.py",
+ deps = [
+ ":pricetag_proto_py_pb2",
+ ],
+)
diff --git a/examples/py_proto_library/MODULE.bazel b/examples/py_proto_library/MODULE.bazel
new file mode 100644
index 0000000..feb938d
--- /dev/null
+++ b/examples/py_proto_library/MODULE.bazel
@@ -0,0 +1,23 @@
+module(
+ name = "rules_python_py_proto_library_example",
+ version = "0.0.0",
+ compatibility_level = 1,
+)
+
+bazel_dep(name = "rules_python", version = "0.17.3")
+
+# The following local_path_override is only needed to run this example as part of our CI.
+local_path_override(
+ module_name = "rules_python",
+ path = "../..",
+)
+
+python = use_extension("@rules_python//python/extensions:python.bzl", "python")
+python.toolchain(
+ configure_coverage_tool = True,
+ python_version = "3.9",
+)
+use_repo(python, "python_3_9")
+
+# We are using rules_proto to define rules_proto targets to be consumed by py_proto_library.
+bazel_dep(name = "rules_proto", version = "5.3.0-21.7")
diff --git a/examples/py_proto_library/WORKSPACE b/examples/py_proto_library/WORKSPACE
new file mode 100644
index 0000000..bf38112
--- /dev/null
+++ b/examples/py_proto_library/WORKSPACE
@@ -0,0 +1,48 @@
+workspace(name = "rules_python_py_proto_library_example")
+
+# The following local_path_override is only needed to run this example as part of our CI.
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+# When not using this example in the rules_python git repo you would load the python
+# rules using http_archive(), as documented in the release notes.
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+# We install the rules_python dependencies using the function below.
+py_repositories()
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+# Then we need to setup dependencies in order to use py_proto_library
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+ name = "rules_proto",
+ sha256 = "dc3fb206a2cb3441b485eb1e423165b231235a1ea9b031b4433cf7bc1fa460dd",
+ strip_prefix = "rules_proto-5.3.0-21.7",
+ urls = [
+ "https://github.com/bazelbuild/rules_proto/archive/refs/tags/5.3.0-21.7.tar.gz",
+ ],
+)
+
+http_archive(
+ name = "com_google_protobuf",
+ sha256 = "75be42bd736f4df6d702a0e4e4d30de9ee40eac024c4b845d17ae4cc831fe4ae",
+ strip_prefix = "protobuf-21.7",
+ urls = [
+ "https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz",
+ "https://github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz",
+ ],
+)
+
+load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
+
+rules_proto_dependencies()
+
+rules_proto_toolchains()
diff --git a/examples/py_proto_library/WORKSPACE.bzlmod b/examples/py_proto_library/WORKSPACE.bzlmod
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/examples/py_proto_library/WORKSPACE.bzlmod
diff --git a/examples/py_proto_library/pricetag.proto b/examples/py_proto_library/pricetag.proto
new file mode 100644
index 0000000..c952248
--- /dev/null
+++ b/examples/py_proto_library/pricetag.proto
@@ -0,0 +1,8 @@
+syntax = "proto3";
+
+package rules_python;
+
+message PriceTag {
+ string name = 2;
+ double cost = 1;
+}
diff --git a/examples/py_proto_library/test.py b/examples/py_proto_library/test.py
new file mode 100644
index 0000000..9f09702
--- /dev/null
+++ b/examples/py_proto_library/test.py
@@ -0,0 +1,17 @@
+import sys
+import unittest
+
+import pricetag_pb2
+
+
+class TestCase(unittest.TestCase):
+ def test_pricetag(self):
+ got = pricetag_pb2.PriceTag(
+ name="dollar",
+ cost=5.00,
+ )
+ self.assertIsNotNone(got)
+
+
+if __name__ == "__main__":
+ unittest.main()
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()
diff --git a/gazelle/.bazelrc b/gazelle/.bazelrc
new file mode 100644
index 0000000..f48d0a9
--- /dev/null
+++ b/gazelle/.bazelrc
@@ -0,0 +1,13 @@
+test --test_output=errors
+
+# Do NOT implicitly create empty __init__.py files in the runfiles tree.
+# By default, these are created in every directory containing Python source code
+# or shared libraries, and every parent directory of those directories,
+# excluding the repo root directory. With this flag set, we are responsible for
+# creating (possibly empty) __init__.py files and adding them to the srcs of
+# Python targets as required.
+build --incompatible_default_to_explicit_init_py
+
+# Windows makes use of runfiles for some rules
+build --enable_runfiles
+startup --windows_enable_symlinks
diff --git a/gazelle/.gitignore b/gazelle/.gitignore
new file mode 100644
index 0000000..8481c96
--- /dev/null
+++ b/gazelle/.gitignore
@@ -0,0 +1,12 @@
+# Bazel directories
+/bazel-*
+/bazel-bin
+/bazel-genfiles
+/bazel-out
+/bazel-testlogs
+user.bazelrc
+
+# Go/Gazelle files
+# These otherwise match patterns above
+!go.mod
+!BUILD.out
diff --git a/gazelle/BUILD.bazel b/gazelle/BUILD.bazel
new file mode 100644
index 0000000..6016145
--- /dev/null
+++ b/gazelle/BUILD.bazel
@@ -0,0 +1,35 @@
+load("@bazel_gazelle//:def.bzl", "gazelle")
+
+# Gazelle configuration options.
+# See https://github.com/bazelbuild/bazel-gazelle#running-gazelle-with-bazel
+# gazelle:prefix github.com/bazelbuild/rules_python/gazelle
+# gazelle:exclude bazel-out
+gazelle(name = "gazelle")
+
+gazelle(
+ name = "gazelle_update_repos",
+ args = [
+ "-from_file=go.mod",
+ "-to_macro=deps.bzl%gazelle_deps",
+ "-prune",
+ ],
+ command = "update-repos",
+)
+
+filegroup(
+ name = "distribution",
+ srcs = [
+ ":BUILD.bazel",
+ ":README.md",
+ ":WORKSPACE",
+ ":def.bzl",
+ ":deps.bzl",
+ ":go.mod",
+ ":go.sum",
+ "//manifest:distribution",
+ "//modules_mapping:distribution",
+ "//python:distribution",
+ "//pythonconfig:distribution",
+ ],
+ visibility = ["@rules_python//:__pkg__"],
+)
diff --git a/gazelle/MODULE.bazel b/gazelle/MODULE.bazel
new file mode 100644
index 0000000..ae94a5f
--- /dev/null
+++ b/gazelle/MODULE.bazel
@@ -0,0 +1,20 @@
+module(
+ name = "rules_python_gazelle_plugin",
+ version = "0.0.0",
+ compatibility_level = 1,
+)
+
+bazel_dep(name = "rules_python", version = "0.18.0")
+bazel_dep(name = "rules_go", version = "0.38.1", repo_name = "io_bazel_rules_go")
+bazel_dep(name = "gazelle", version = "0.31.0", repo_name = "bazel_gazelle")
+
+go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
+go_deps.from_file(go_mod = "//:go.mod")
+use_repo(
+ go_deps,
+ "com_github_bazelbuild_buildtools",
+ "com_github_bmatcuk_doublestar",
+ "com_github_emirpasic_gods",
+ "com_github_ghodss_yaml",
+ "in_gopkg_yaml_v2",
+)
diff --git a/gazelle/README.md b/gazelle/README.md
new file mode 100644
index 0000000..ba8520d
--- /dev/null
+++ b/gazelle/README.md
@@ -0,0 +1,263 @@
+# Python Gazelle plugin
+
+[Gazelle](https://github.com/bazelbuild/bazel-gazelle)
+is a build file generator for Bazel projects. It can create new BUILD.bazel files for a project that follows language conventions, and it can update existing build files to include new sources, dependencies, and options.
+
+Gazelle may be run by Bazel using the gazelle rule, or it may be installed and run as a command line tool.
+
+This directory contains a plugin for
+[Gazelle](https://github.com/bazelbuild/bazel-gazelle)
+that generates BUILD files content for Python code.
+
+The following instructions are for when you use [bzlmod](https://docs.bazel.build/versions/5.0.0/bzlmod.html).
+Please refer to older documentation that includes instructions on how to use Gazelle
+without using bzlmod as your dependency manager.
+
+## Example
+
+We have an example of using Gazelle with Python located [here](https://github.com/bazelbuild/rules_python/tree/main/examples/bzlmod).
+A fully-working example without using bzlmod is in [`examples/build_file_generation`](../examples/build_file_generation).
+
+The following documentation covers using bzlmod.
+
+## Adding Gazelle to your project
+
+First, you'll need to add Gazelle to your `MODULES.bazel` file.
+Get the current version of Gazelle from there releases here: https://github.com/bazelbuild/bazel-gazelle/releases/.
+
+
+See the installation `MODULE.bazel` snippet on the Releases page:
+https://github.com/bazelbuild/rules_python/releases in order to configure rules_python.
+
+You will also need to add the `bazel_dep` for configuration for `rules_python_gazelle_plugin`.
+
+Here is a snippet of a `MODULE.bazel` file.
+
+```starlark
+# The following stanza defines the dependency rules_python.
+bazel_dep(name = "rules_python", version = "0.22.0")
+
+# The following stanza defines the dependency rules_python_gazelle_plugin.
+# For typical setups you set the version.
+bazel_dep(name = "rules_python_gazelle_plugin", version = "0.22.0")
+
+# The following stanza defines the dependency gazelle.
+bazel_dep(name = "gazelle", version = "0.31.0", repo_name = "bazel_gazelle")
+
+# Import the python repositories generated by the given module extension into the scope of the current module.
+use_repo(python, "python3_9")
+use_repo(python, "python3_9_toolchains")
+
+# Register an already-defined toolchain so that Bazel can use it during toolchain resolution.
+register_toolchains(
+ "@python3_9_toolchains//:all",
+)
+
+# Use the pip extension
+pip = use_extension("@rules_python//python:extensions.bzl", "pip")
+
+# Use the extension to call the `pip_repository` rule that invokes `pip`, with `incremental` set.
+# Accepts a locked/compiled requirements file and installs the dependencies listed within.
+# Those dependencies become available in a generated `requirements.bzl` file.
+# You can instead check this `requirements.bzl` file into your repo.
+# Because this project has different requirements for windows vs other
+# operating systems, we have requirements for each.
+pip.parse(
+ name = "pip",
+ # When using gazelle you must use set the following flag
+ # in order for the generation of gazelle dependency resolution.
+ incompatible_generate_aliases = True,
+ requirements_lock = "//:requirements_lock.txt",
+ requirements_windows = "//:requirements_windows.txt",
+)
+
+# Imports the pip toolchain generated by the given module extension into the scope of the current module.
+use_repo(pip, "pip")
+```
+Next, we'll fetch metadata about your Python dependencies, so that gazelle can
+determine which package a given import statement comes from. This is provided
+by the `modules_mapping` rule. We'll make a target for consuming this
+`modules_mapping`, and writing it as a manifest file for Gazelle to read.
+This is checked into the repo for speed, as it takes some time to calculate
+in a large monorepo.
+
+Gazelle will walk up the filesystem from a Python file to find this metadata,
+looking for a file called `gazelle_python.yaml` in an ancestor folder of the Python code.
+Create an empty file with this name. It might be next to your `requirements.txt` file.
+(You can just use `touch` at this point, it just needs to exist.)
+
+To keep the metadata updated, put this in your `BUILD.bazel` file next to `gazelle_python.yaml`:
+
+```starlark
+load("@pip//:requirements.bzl", "all_whl_requirements")
+load("@rules_python_gazelle_plugin//manifest:defs.bzl", "gazelle_python_manifest")
+load("@rules_python_gazelle_plugin//modules_mapping:def.bzl", "modules_mapping")
+
+# This rule fetches the metadata for python packages we depend on. That data is
+# required for the gazelle_python_manifest rule to update our manifest file.
+modules_mapping(
+ name = "modules_map",
+ wheels = all_whl_requirements,
+)
+
+# Gazelle python extension needs a manifest file mapping from
+# an import to the installed package that provides it.
+# This macro produces two targets:
+# - //:gazelle_python_manifest.update can be used with `bazel run`
+# to recalculate the manifest
+# - //:gazelle_python_manifest.test is a test target ensuring that
+# the manifest doesn't need to be updated
+gazelle_python_manifest(
+ name = "gazelle_python_manifest",
+ modules_mapping = ":modules_map",
+ # This is what we called our `pip_install` rule, where third-party
+ # python libraries are loaded in BUILD files.
+ pip_repository_name = "pip",
+ # This should point to wherever we declare our python dependencies
+ # (the same as what we passed to the modules_mapping rule in WORKSPACE)
+ requirements = "//:requirements_lock.txt",
+ # NOTE: we can use this flag in order to make our setup compatible with
+ # bzlmod.
+ use_pip_repository_aliases = True,
+)
+```
+
+Finally, you create a target that you'll invoke to run the Gazelle tool
+with the rules_python extension included. This typically goes in your root
+`/BUILD.bazel` file:
+
+```starlark
+load("@bazel_gazelle//:def.bzl", "gazelle")
+load("@rules_python_gazelle_plugin//:def.bzl", "GAZELLE_PYTHON_RUNTIME_DEPS")
+
+# Our gazelle target points to the python gazelle binary.
+# This is the simple case where we only need one language supported.
+# If you also had proto, go, or other gazelle-supported languages,
+# you would also need a gazelle_binary rule.
+# See https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.rst#example
+gazelle(
+ name = "gazelle",
+ data = GAZELLE_PYTHON_RUNTIME_DEPS,
+ gazelle = "@rules_python_gazelle_plugin//python:gazelle_binary",
+)
+```
+
+That's it, now you can finally run `bazel run //:gazelle` anytime
+you edit Python code, and it should update your `BUILD` files correctly.
+
+## Usage
+
+Gazelle is non-destructive.
+It will try to leave your edits to BUILD files alone, only making updates to `py_*` targets.
+However it will remove dependencies that appear to be unused, so it's a
+good idea to check in your work before running Gazelle so you can easily
+revert any changes it made.
+
+The rules_python extension assumes some conventions about your Python code.
+These are noted below, and might require changes to your existing code.
+
+Note that the `gazelle` program has multiple commands. At present, only the `update` command (the default) does anything for Python code.
+
+### Directives
+
+You can configure the extension using directives, just like for other
+languages. These are just comments in the `BUILD.bazel` file which
+govern behavior of the extension when processing files under that
+folder.
+
+See https://github.com/bazelbuild/bazel-gazelle#directives
+for some general directives that may be useful.
+In particular, the `resolve` directive is language-specific
+and can be used with Python.
+Examples of these directives in use can be found in the
+/gazelle/testdata folder in the rules_python repo.
+
+Python-specific directives are as follows:
+
+| **Directive** | **Default value** |
+|--------------------------------------|-------------------|
+| `# gazelle:python_extension` | `enabled` |
+| Controls whether the Python extension is enabled or not. Sub-packages inherit this value. Can be either "enabled" or "disabled". | |
+| `# gazelle:python_root` | n/a |
+| Sets a Bazel package as a Python root. This is used on monorepos with multiple Python projects that don't share the top-level of the workspace as the root. | |
+| `# gazelle:python_manifest_file_name`| `gazelle_python.yaml` |
+| Overrides the default manifest file name. | |
+| `# gazelle:python_ignore_files` | n/a |
+| Controls the files which are ignored from the generated targets. | |
+| `# gazelle:python_ignore_dependencies`| n/a |
+| Controls the ignored dependencies from the generated targets. | |
+| `# gazelle:python_validate_import_statements`| `true` |
+| Controls whether the Python import statements should be validated. Can be "true" or "false" | |
+| `# gazelle:python_generation_mode`| `package` |
+| Controls the target generation mode. Can be "package" or "project" | |
+| `# gazelle:python_library_naming_convention`| `$package_name$` |
+| Controls the `py_library` naming convention. It interpolates $package_name$ with the Bazel package name. E.g. if the Bazel package name is `foo`, setting this to `$package_name$_my_lib` would result in a generated target named `foo_my_lib`. | |
+| `# gazelle:python_binary_naming_convention` | `$package_name$_bin` |
+| Controls the `py_binary` naming convention. Follows the same interpolation rules as `python_library_naming_convention`. | |
+| `# gazelle:python_test_naming_convention` | `$package_name$_test` |
+| Controls the `py_test` naming convention. Follows the same interpolation rules as `python_library_naming_convention`. | |
+| `# gazelle:resolve py ...` | n/a |
+| Instructs the plugin what target to add as a dependency to satisfy a given import statement. The syntax is `# gazelle:resolve py import-string label` where `import-string` is the symbol in the python `import` statement, and `label` is the Bazel label that Gazelle should write in `deps`. | |
+
+### Libraries
+
+Python source files are those ending in `.py` but not ending in `_test.py`.
+
+First, we look for the nearest ancestor BUILD file starting from the folder
+containing the Python source file.
+
+If there is no `py_library` in this BUILD file, one is created, using the
+package name as the target's name. This makes it the default target in the
+package.
+
+Next, all source files are collected into the `srcs` of the `py_library`.
+
+Finally, the `import` statements in the source files are parsed, and
+dependencies are added to the `deps` attribute.
+
+### Unit Tests
+
+A `py_test` target is added to the BUILD file when gazelle encounters
+a file named `__test__.py`.
+Often, Python unit test files are named with the suffix `_test`.
+For example, if we had a folder that is a package named "foo" we could have a Python file named `foo_test.py`
+and gazelle would create a `py_test` block for the file.
+
+The following is an example of a `py_test` target that gazelle would add when
+it encounters a file named `__test__.py`.
+
+```starlark
+py_test(
+ name = "build_file_generation_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":build_file_generation"],
+)
+```
+
+You can control the naming convention for test targets by adding a gazelle directive named
+`# gazelle:python_test_naming_convention`. See the instructions in the section above that
+covers directives.
+
+### Binaries
+
+When a `__main__.py` file is encountered, this indicates the entry point
+of a Python program.
+
+A `py_binary` target will be created, named `[package]_bin`.
+
+## Developer Notes
+
+Gazelle extensions are written in Go. This gazelle plugin is a hybrid, as it uses Go to execute a
+Python interpreter as a subprocess to parse Python source files.
+See the gazelle documentation https://github.com/bazelbuild/bazel-gazelle/blob/master/extend.md
+for more information on extending Gazelle.
+
+If you add new Go dependencies to the plugin source code, you need to "tidy" the go.mod file.
+After changing that file, run `go mod tidy` or `bazel run @go_sdk//:bin/go -- mod tidy`
+to update the go.mod and go.sum files. Then run `bazel run //:update_go_deps` to have gazelle
+add the new dependenies to the deps.bzl file. The deps.bzl file is used as defined in our /WORKSPACE
+to include the external repos Bazel loads Go dependencies from.
+
+Then after editing Go code, run `bazel run //:gazelle` to generate/update the rules in the
+BUILD.bazel files in our repo.
diff --git a/gazelle/WORKSPACE b/gazelle/WORKSPACE
new file mode 100644
index 0000000..eef16e9
--- /dev/null
+++ b/gazelle/WORKSPACE
@@ -0,0 +1,47 @@
+workspace(name = "rules_python_gazelle_plugin")
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+ name = "io_bazel_rules_go",
+ sha256 = "099a9fb96a376ccbbb7d291ed4ecbdfd42f6bc822ab77ae6f1b5cb9e914e94fa",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.35.0/rules_go-v0.35.0.zip",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.35.0/rules_go-v0.35.0.zip",
+ ],
+)
+
+http_archive(
+ name = "bazel_gazelle",
+ sha256 = "29d5dafc2a5582995488c6735115d1d366fcd6a0fc2e2a153f02988706349825",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.31.0/bazel-gazelle-v0.31.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.31.0/bazel-gazelle-v0.31.0.tar.gz",
+ ],
+)
+
+load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies")
+load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies")
+
+go_rules_dependencies()
+
+go_register_toolchains(version = "1.19.4")
+
+gazelle_dependencies()
+
+local_repository(
+ name = "rules_python",
+ path = "..",
+)
+
+load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+load("//:deps.bzl", _py_gazelle_deps = "gazelle_deps")
+
+# gazelle:repository_macro deps.bzl%gazelle_deps
+_py_gazelle_deps()
diff --git a/gazelle/def.bzl b/gazelle/def.bzl
new file mode 100644
index 0000000..80b1157
--- /dev/null
+++ b/gazelle/def.bzl
@@ -0,0 +1,21 @@
+# 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.
+
+"""This module contains the Gazelle runtime dependencies for the Python extension.
+"""
+
+GAZELLE_PYTHON_RUNTIME_DEPS = [
+ "@rules_python_gazelle_plugin//python:parse",
+ "@rules_python_gazelle_plugin//python:std_modules",
+]
diff --git a/gazelle/deps.bzl b/gazelle/deps.bzl
new file mode 100644
index 0000000..26f8c66
--- /dev/null
+++ b/gazelle/deps.bzl
@@ -0,0 +1,261 @@
+# 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.
+
+"This file managed by `bazel run //:gazelle_update_repos`"
+
+load("@bazel_gazelle//:deps.bzl", _go_repository = "go_repository")
+
+def go_repository(name, **kwargs):
+ if name not in native.existing_rules():
+ _go_repository(name = name, **kwargs)
+
+def gazelle_deps():
+ "Fetch go dependencies"
+ go_repository(
+ name = "co_honnef_go_tools",
+ importpath = "honnef.co/go/tools",
+ sum = "h1:/hemPrYIhOhy8zYrNj+069zDB68us2sMGsfkFJO0iZs=",
+ version = "v0.0.0-20190523083050-ea95bdfd59fc",
+ )
+
+ go_repository(
+ name = "com_github_bazelbuild_buildtools",
+ build_naming_convention = "go_default_library",
+ importpath = "github.com/bazelbuild/buildtools",
+ sum = "h1:jhiMzJ+8unnLRtV8rpbWBFE9pFNzIqgUTyZU5aA++w8=",
+ version = "v0.0.0-20221004120235-7186f635531b",
+ )
+
+ go_repository(
+ name = "com_github_bmatcuk_doublestar",
+ importpath = "github.com/bmatcuk/doublestar",
+ sum = "h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=",
+ version = "v1.3.4",
+ )
+
+ go_repository(
+ name = "com_github_burntsushi_toml",
+ importpath = "github.com/BurntSushi/toml",
+ sum = "h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=",
+ version = "v0.3.1",
+ )
+ go_repository(
+ name = "com_github_census_instrumentation_opencensus_proto",
+ importpath = "github.com/census-instrumentation/opencensus-proto",
+ sum = "h1:glEXhBS5PSLLv4IXzLA5yPRVX4bilULVyxxbrfOtDAk=",
+ version = "v0.2.1",
+ )
+ go_repository(
+ name = "com_github_chzyer_logex",
+ importpath = "github.com/chzyer/logex",
+ sum = "h1:Swpa1K6QvQznwJRcfTfQJmTE72DqScAa40E+fbHEXEE=",
+ version = "v1.1.10",
+ )
+ go_repository(
+ name = "com_github_chzyer_readline",
+ importpath = "github.com/chzyer/readline",
+ sum = "h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8=",
+ version = "v0.0.0-20180603132655-2972be24d48e",
+ )
+ go_repository(
+ name = "com_github_chzyer_test",
+ importpath = "github.com/chzyer/test",
+ sum = "h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8=",
+ version = "v0.0.0-20180213035817-a1ea475d72b1",
+ )
+ go_repository(
+ name = "com_github_client9_misspell",
+ importpath = "github.com/client9/misspell",
+ sum = "h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=",
+ version = "v0.3.4",
+ )
+ go_repository(
+ name = "com_github_emirpasic_gods",
+ importpath = "github.com/emirpasic/gods",
+ sum = "h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=",
+ version = "v1.18.1",
+ )
+ go_repository(
+ name = "com_github_envoyproxy_go_control_plane",
+ importpath = "github.com/envoyproxy/go-control-plane",
+ sum = "h1:4cmBvAEBNJaGARUEs3/suWRyfyBfhf7I60WBZq+bv2w=",
+ version = "v0.9.1-0.20191026205805-5f8ba28d4473",
+ )
+ go_repository(
+ name = "com_github_envoyproxy_protoc_gen_validate",
+ importpath = "github.com/envoyproxy/protoc-gen-validate",
+ sum = "h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A=",
+ version = "v0.1.0",
+ )
+
+ go_repository(
+ name = "com_github_ghodss_yaml",
+ importpath = "github.com/ghodss/yaml",
+ sum = "h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=",
+ version = "v1.0.0",
+ )
+ go_repository(
+ name = "com_github_golang_glog",
+ importpath = "github.com/golang/glog",
+ sum = "h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=",
+ version = "v0.0.0-20160126235308-23def4e6c14b",
+ )
+ go_repository(
+ name = "com_github_golang_mock",
+ importpath = "github.com/golang/mock",
+ sum = "h1:G5FRp8JnTd7RQH5kemVNlMeyXQAztQ3mOWV95KxsXH8=",
+ version = "v1.1.1",
+ )
+ go_repository(
+ name = "com_github_golang_protobuf",
+ importpath = "github.com/golang/protobuf",
+ sum = "h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM=",
+ version = "v1.4.3",
+ )
+ go_repository(
+ name = "com_github_google_go_cmp",
+ importpath = "github.com/google/go-cmp",
+ sum = "h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=",
+ version = "v0.5.9",
+ )
+
+ go_repository(
+ name = "com_github_prometheus_client_model",
+ importpath = "github.com/prometheus/client_model",
+ sum = "h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=",
+ version = "v0.0.0-20190812154241-14fe0d1b01d4",
+ )
+ go_repository(
+ name = "com_github_yuin_goldmark",
+ importpath = "github.com/yuin/goldmark",
+ sum = "h1:fVcFKWvrslecOb/tg+Cc05dkeYx540o0FuFt3nUVDoE=",
+ version = "v1.4.13",
+ )
+ go_repository(
+ name = "com_google_cloud_go",
+ importpath = "cloud.google.com/go",
+ sum = "h1:e0WKqKTd5BnrG8aKH3J3h+QvEIQtSUcf2n5UZ5ZgLtQ=",
+ version = "v0.26.0",
+ )
+ go_repository(
+ name = "in_gopkg_check_v1",
+ importpath = "gopkg.in/check.v1",
+ sum = "h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=",
+ version = "v0.0.0-20161208181325-20d25e280405",
+ )
+ go_repository(
+ name = "in_gopkg_yaml_v2",
+ importpath = "gopkg.in/yaml.v2",
+ sum = "h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=",
+ version = "v2.4.0",
+ )
+ go_repository(
+ name = "net_starlark_go",
+ importpath = "go.starlark.net",
+ sum = "h1:xwwDQW5We85NaTk2APgoN9202w/l0DVGp+GZMfsrh7s=",
+ version = "v0.0.0-20210223155950-e043a3d3c984",
+ )
+ go_repository(
+ name = "org_golang_google_appengine",
+ importpath = "google.golang.org/appengine",
+ sum = "h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=",
+ version = "v1.4.0",
+ )
+ go_repository(
+ name = "org_golang_google_genproto",
+ importpath = "google.golang.org/genproto",
+ sum = "h1:+kGHl1aib/qcwaRi1CbqBZ1rk19r85MNUf8HaBghugY=",
+ version = "v0.0.0-20200526211855-cb27e3aa2013",
+ )
+ go_repository(
+ name = "org_golang_google_grpc",
+ importpath = "google.golang.org/grpc",
+ sum = "h1:rRYRFMVgRv6E0D70Skyfsr28tDXIuuPZyWGMPdMcnXg=",
+ version = "v1.27.0",
+ )
+ go_repository(
+ name = "org_golang_google_protobuf",
+ importpath = "google.golang.org/protobuf",
+ sum = "h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=",
+ version = "v1.25.0",
+ )
+ go_repository(
+ name = "org_golang_x_crypto",
+ importpath = "golang.org/x/crypto",
+ sum = "h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=",
+ version = "v0.0.0-20190308221718-c2843e01d9a2",
+ )
+ go_repository(
+ name = "org_golang_x_exp",
+ importpath = "golang.org/x/exp",
+ sum = "h1:c2HOrn5iMezYjSlGPncknSEr/8x5LELb/ilJbXi9DEA=",
+ version = "v0.0.0-20190121172915-509febef88a4",
+ )
+ go_repository(
+ name = "org_golang_x_lint",
+ importpath = "golang.org/x/lint",
+ sum = "h1:XQyxROzUlZH+WIQwySDgnISgOivlhjIEwaQaJEJrrN0=",
+ version = "v0.0.0-20190313153728-d0100b6bd8b3",
+ )
+ go_repository(
+ name = "org_golang_x_mod",
+ importpath = "golang.org/x/mod",
+ sum = "h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=",
+ version = "v0.6.0-dev.0.20220419223038-86c51ed26bb4",
+ )
+ go_repository(
+ name = "org_golang_x_net",
+ importpath = "golang.org/x/net",
+ sum = "h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=",
+ version = "v0.0.0-20220722155237-a158d28d115b",
+ )
+ go_repository(
+ name = "org_golang_x_oauth2",
+ importpath = "golang.org/x/oauth2",
+ sum = "h1:vEDujvNQGv4jgYKudGeI/+DAX4Jffq6hpD55MmoEvKs=",
+ version = "v0.0.0-20180821212333-d2e6202438be",
+ )
+ go_repository(
+ name = "org_golang_x_sync",
+ importpath = "golang.org/x/sync",
+ sum = "h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw=",
+ version = "v0.0.0-20220722155255-886fb9371eb4",
+ )
+ go_repository(
+ name = "org_golang_x_sys",
+ importpath = "golang.org/x/sys",
+ sum = "h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc=",
+ version = "v0.0.0-20221010170243-090e33056c14",
+ )
+ go_repository(
+ name = "org_golang_x_text",
+ importpath = "golang.org/x/text",
+ sum = "h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=",
+ version = "v0.3.7",
+ )
+ go_repository(
+ name = "org_golang_x_tools",
+ build_directives = [
+ "gazelle:exclude **/testdata/**/*",
+ ],
+ importpath = "golang.org/x/tools",
+ sum = "h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU=",
+ version = "v0.1.12",
+ )
+ go_repository(
+ name = "org_golang_x_xerrors",
+ importpath = "golang.org/x/xerrors",
+ sum = "h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=",
+ version = "v0.0.0-20200804184101-5ec99f83aff1",
+ )
diff --git a/gazelle/go.mod b/gazelle/go.mod
new file mode 100644
index 0000000..1d1cee7
--- /dev/null
+++ b/gazelle/go.mod
@@ -0,0 +1,20 @@
+module github.com/bazelbuild/rules_python/gazelle
+
+go 1.19
+
+require (
+ github.com/bazelbuild/bazel-gazelle v0.31.1
+ github.com/bazelbuild/buildtools v0.0.0-20230510134650-37bd1811516d
+ github.com/bazelbuild/rules_go v0.39.1
+ github.com/bmatcuk/doublestar v1.3.4
+ github.com/emirpasic/gods v1.18.1
+ github.com/ghodss/yaml v1.0.0
+ gopkg.in/yaml.v2 v2.4.0
+)
+
+require (
+ github.com/google/go-cmp v0.5.9 // indirect
+ golang.org/x/mod v0.10.0 // indirect
+ golang.org/x/sys v0.8.0 // indirect
+ golang.org/x/tools v0.9.1 // indirect
+)
diff --git a/gazelle/go.sum b/gazelle/go.sum
new file mode 100644
index 0000000..ba2c8bf
--- /dev/null
+++ b/gazelle/go.sum
@@ -0,0 +1,94 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/bazelbuild/bazel-gazelle v0.31.1 h1:ROyUyUHzoEdvoOs1e0haxJx1l5EjZX6AOqiKdVlaBbg=
+github.com/bazelbuild/bazel-gazelle v0.31.1/go.mod h1:Ul0pqz50f5wxz0QNzsZ+mrEu4AVAVJZEB5xLnHgIG9c=
+github.com/bazelbuild/buildtools v0.0.0-20230510134650-37bd1811516d h1:Fl1FfItZp34QIQmmDTbZXHB5XA6JfbNNfH7tRRGWvQo=
+github.com/bazelbuild/buildtools v0.0.0-20230510134650-37bd1811516d/go.mod h1:689QdV3hBP7Vo9dJMmzhoYIyo/9iMhEmHkJcnaPRCbo=
+github.com/bazelbuild/rules_go v0.39.1 h1:wkJLUDx59dntWMghuL8++GteoU1To6sRoKJXuyFtmf8=
+github.com/bazelbuild/rules_go v0.39.1/go.mod h1:TMHmtfpvyfsxaqfL9WnahCsXMWDMICTw7XeK9yVb+YU=
+github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQrS0=
+github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
+github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
+github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
+github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
+github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
+github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
+github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
+github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
+github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
+github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
+github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+go.starlark.net v0.0.0-20210223155950-e043a3d3c984/go.mod h1:t3mmBBPzAVvK0L0n1drDmrQsJ8FoIx4INCqVMTr/Zo0=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
+golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
+golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
+golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
+google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
+google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
+gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/gazelle/manifest/BUILD.bazel b/gazelle/manifest/BUILD.bazel
new file mode 100644
index 0000000..fc7fa09
--- /dev/null
+++ b/gazelle/manifest/BUILD.bazel
@@ -0,0 +1,29 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "manifest",
+ srcs = ["manifest.go"],
+ importpath = "github.com/bazelbuild/rules_python/gazelle/manifest",
+ visibility = ["//visibility:public"],
+ deps = [
+ "@com_github_emirpasic_gods//sets/treeset",
+ "@in_gopkg_yaml_v2//:yaml_v2",
+ ],
+)
+
+go_test(
+ name = "manifest_test",
+ srcs = ["manifest_test.go"],
+ data = glob(["testdata/**"]),
+ deps = [":manifest"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]) + [
+ "//manifest/generate:distribution",
+ "//manifest/hasher:distribution",
+ "//manifest/test:distribution",
+ ],
+ visibility = ["//:__pkg__"],
+)
diff --git a/gazelle/manifest/defs.bzl b/gazelle/manifest/defs.bzl
new file mode 100644
index 0000000..f1266a0
--- /dev/null
+++ b/gazelle/manifest/defs.bzl
@@ -0,0 +1,181 @@
+# 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.
+
+"""This module provides the gazelle_python_manifest macro that contains targets
+for updating and testing the Gazelle manifest file.
+"""
+
+load("@io_bazel_rules_go//go:def.bzl", "GoSource", "go_binary", "go_test")
+
+def gazelle_python_manifest(
+ name,
+ requirements,
+ modules_mapping,
+ pip_repository_name = "",
+ pip_deps_repository_name = "",
+ manifest = ":gazelle_python.yaml",
+ use_pip_repository_aliases = False):
+ """A macro for defining the updating and testing targets for the Gazelle manifest file.
+
+ Args:
+ name: the name used as a base for the targets.
+ requirements: the target for the requirements.txt file or a list of
+ requirements files that will be concatenated before passing on to
+ the manifest generator.
+ pip_repository_name: the name of the pip_install or pip_repository target.
+ use_pip_repository_aliases: boolean flag to enable using user-friendly
+ python package aliases.
+ pip_deps_repository_name: deprecated - the old pip_install target name.
+ modules_mapping: the target for the generated modules_mapping.json file.
+ manifest: the target for the Gazelle manifest file.
+ """
+ if pip_deps_repository_name != "":
+ # buildifier: disable=print
+ print("DEPRECATED pip_deps_repository_name in //{}:{}. Please use pip_repository_name instead.".format(
+ native.package_name(),
+ name,
+ ))
+ pip_repository_name = pip_deps_repository_name
+
+ if pip_repository_name == "":
+ # This is a temporary check while pip_deps_repository_name exists as deprecated.
+ fail("pip_repository_name must be set in //{}:{}".format(native.package_name(), name))
+
+ update_target = "{}.update".format(name)
+ update_target_label = "//{}:{}".format(native.package_name(), update_target)
+
+ manifest_generator_hash = Label("//manifest/generate:generate_lib_sources_hash")
+
+ if type(requirements) == "list":
+ native.genrule(
+ name = name + "_requirements_gen",
+ srcs = sorted(requirements),
+ outs = [name + "_requirements.txt"],
+ cmd_bash = "cat $(SRCS) > $@",
+ cmd_bat = "type $(SRCS) > $@",
+ )
+ requirements = name + "_requirements_gen"
+
+ update_args = [
+ "--manifest-generator-hash",
+ "$(rootpath {})".format(manifest_generator_hash),
+ "--requirements",
+ "$(rootpath {})".format(requirements),
+ "--pip-repository-name",
+ pip_repository_name,
+ "--modules-mapping",
+ "$(rootpath {})".format(modules_mapping),
+ "--output",
+ "$(rootpath {})".format(manifest),
+ "--update-target",
+ update_target_label,
+ ]
+
+ if use_pip_repository_aliases:
+ update_args += [
+ "--use-pip-repository-aliases",
+ "true",
+ ]
+
+ go_binary(
+ name = update_target,
+ embed = [Label("//manifest/generate:generate_lib")],
+ data = [
+ manifest,
+ modules_mapping,
+ requirements,
+ manifest_generator_hash,
+ ],
+ args = update_args,
+ visibility = ["//visibility:private"],
+ tags = ["manual"],
+ )
+
+ go_test(
+ name = "{}.test".format(name),
+ srcs = [Label("//manifest/test:test.go")],
+ data = [
+ manifest,
+ requirements,
+ manifest_generator_hash,
+ ],
+ env = {
+ "_TEST_MANIFEST": "$(rootpath {})".format(manifest),
+ "_TEST_MANIFEST_GENERATOR_HASH": "$(rootpath {})".format(manifest_generator_hash),
+ "_TEST_REQUIREMENTS": "$(rootpath {})".format(requirements),
+ },
+ rundir = ".",
+ deps = [Label("//manifest")],
+ size = "small",
+ )
+
+ native.filegroup(
+ name = name,
+ srcs = [manifest],
+ tags = ["manual"],
+ visibility = ["//visibility:public"],
+ )
+
+# buildifier: disable=provider-params
+AllSourcesInfo = provider(fields = {"all_srcs": "All sources collected from the target and dependencies."})
+
+_rules_python_workspace = Label("@rules_python//:WORKSPACE")
+
+def _get_all_sources_impl(target, ctx):
+ is_rules_python = target.label.workspace_name == _rules_python_workspace.workspace_name
+ if not is_rules_python:
+ # Avoid adding third-party dependency files to the checksum of the srcs.
+ return AllSourcesInfo(all_srcs = depset())
+ srcs = depset(
+ target[GoSource].orig_srcs,
+ transitive = [dep[AllSourcesInfo].all_srcs for dep in ctx.rule.attr.deps],
+ )
+ return [AllSourcesInfo(all_srcs = srcs)]
+
+_get_all_sources = aspect(
+ implementation = _get_all_sources_impl,
+ attr_aspects = ["deps"],
+)
+
+def _sources_hash_impl(ctx):
+ all_srcs = ctx.attr.go_library[AllSourcesInfo].all_srcs
+ hash_file = ctx.actions.declare_file(ctx.attr.name + ".hash")
+ args = ctx.actions.args()
+ args.add(hash_file)
+ args.add_all(all_srcs)
+ ctx.actions.run(
+ outputs = [hash_file],
+ inputs = all_srcs,
+ arguments = [args],
+ executable = ctx.executable._hasher,
+ )
+ return [DefaultInfo(
+ files = depset([hash_file]),
+ runfiles = ctx.runfiles([hash_file]),
+ )]
+
+sources_hash = rule(
+ _sources_hash_impl,
+ attrs = {
+ "go_library": attr.label(
+ aspects = [_get_all_sources],
+ providers = [GoSource],
+ ),
+ "_hasher": attr.label(
+ cfg = "exec",
+ default = Label("//manifest/hasher"),
+ executable = True,
+ ),
+ },
+)
diff --git a/gazelle/manifest/generate/BUILD.bazel b/gazelle/manifest/generate/BUILD.bazel
new file mode 100644
index 0000000..96248f4
--- /dev/null
+++ b/gazelle/manifest/generate/BUILD.bazel
@@ -0,0 +1,28 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+load("//manifest:defs.bzl", "sources_hash")
+
+go_library(
+ name = "generate_lib",
+ srcs = ["generate.go"],
+ importpath = "github.com/bazelbuild/rules_python/gazelle/manifest/generate",
+ visibility = ["//visibility:public"],
+ deps = ["//manifest"],
+)
+
+sources_hash(
+ name = "generate_lib_sources_hash",
+ go_library = ":generate_lib",
+ visibility = ["//visibility:public"],
+)
+
+go_binary(
+ name = "generate",
+ embed = [":generate_lib"],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//manifest:__pkg__"],
+)
diff --git a/gazelle/manifest/generate/generate.go b/gazelle/manifest/generate/generate.go
new file mode 100644
index 0000000..1f56e63
--- /dev/null
+++ b/gazelle/manifest/generate/generate.go
@@ -0,0 +1,196 @@
+// 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.
+
+/*
+generate.go is a program that generates the Gazelle YAML manifest.
+
+The Gazelle manifest is a file that contains extra information required when
+generating the Bazel BUILD files.
+*/
+package main
+
+import (
+ "encoding/json"
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "strings"
+
+ "github.com/bazelbuild/rules_python/gazelle/manifest"
+)
+
+func init() {
+ if os.Getenv("BUILD_WORKSPACE_DIRECTORY") == "" {
+ log.Fatalln("ERROR: this program must run under Bazel")
+ }
+}
+
+func main() {
+ var (
+ manifestGeneratorHashPath string
+ requirementsPath string
+ pipRepositoryName string
+ usePipRepositoryAliases bool
+ modulesMappingPath string
+ outputPath string
+ updateTarget string
+ )
+ flag.StringVar(
+ &manifestGeneratorHashPath,
+ "manifest-generator-hash",
+ "",
+ "The file containing the hash for the source code of the manifest generator."+
+ "This is important to force manifest updates when the generator logic changes.")
+ flag.StringVar(
+ &requirementsPath,
+ "requirements",
+ "",
+ "The requirements.txt file.")
+ flag.StringVar(
+ &pipRepositoryName,
+ "pip-repository-name",
+ "",
+ "The name of the pip_install or pip_repository target.")
+ flag.BoolVar(
+ &usePipRepositoryAliases,
+ "use-pip-repository-aliases",
+ false,
+ "Whether to use the pip-repository aliases, which are generated when passing 'incompatible_generate_aliases = True'.")
+ flag.StringVar(
+ &modulesMappingPath,
+ "modules-mapping",
+ "",
+ "The modules_mapping.json file.")
+ flag.StringVar(
+ &outputPath,
+ "output",
+ "",
+ "The output YAML manifest file.")
+ flag.StringVar(
+ &updateTarget,
+ "update-target",
+ "",
+ "The Bazel target to update the YAML manifest file.")
+ flag.Parse()
+
+ if requirementsPath == "" {
+ log.Fatalln("ERROR: --requirements must be set")
+ }
+
+ if modulesMappingPath == "" {
+ log.Fatalln("ERROR: --modules-mapping must be set")
+ }
+
+ if outputPath == "" {
+ log.Fatalln("ERROR: --output must be set")
+ }
+
+ if updateTarget == "" {
+ log.Fatalln("ERROR: --update-target must be set")
+ }
+
+ modulesMapping, err := unmarshalJSON(modulesMappingPath)
+ if err != nil {
+ log.Fatalf("ERROR: %v\n", err)
+ }
+
+ header := generateHeader(updateTarget)
+
+ manifestFile := manifest.NewFile(&manifest.Manifest{
+ ModulesMapping: modulesMapping,
+ PipRepository: &manifest.PipRepository{
+ Name: pipRepositoryName,
+ UsePipRepositoryAliases: usePipRepositoryAliases,
+ },
+ })
+ if err := writeOutput(
+ outputPath,
+ header,
+ manifestFile,
+ manifestGeneratorHashPath,
+ requirementsPath,
+ ); err != nil {
+ log.Fatalf("ERROR: %v\n", err)
+ }
+}
+
+// unmarshalJSON returns the parsed mapping from the given JSON file path.
+func unmarshalJSON(jsonPath string) (map[string]string, error) {
+ file, err := os.Open(jsonPath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to unmarshal JSON file: %w", err)
+ }
+ defer file.Close()
+
+ decoder := json.NewDecoder(file)
+ output := make(map[string]string)
+ if err := decoder.Decode(&output); err != nil {
+ return nil, fmt.Errorf("failed to unmarshal JSON file: %w", err)
+ }
+
+ return output, nil
+}
+
+// generateHeader generates the YAML header human-readable comment.
+func generateHeader(updateTarget string) string {
+ var header strings.Builder
+ header.WriteString("# GENERATED FILE - DO NOT EDIT!\n")
+ header.WriteString("#\n")
+ header.WriteString("# To update this file, run:\n")
+ header.WriteString(fmt.Sprintf("# bazel run %s\n", updateTarget))
+ return header.String()
+}
+
+// writeOutput writes to the final file the header and manifest structure.
+func writeOutput(
+ outputPath string,
+ header string,
+ manifestFile *manifest.File,
+ manifestGeneratorHashPath string,
+ requirementsPath string,
+) error {
+ stat, err := os.Stat(outputPath)
+ if err != nil {
+ return fmt.Errorf("failed to write output: %w", err)
+ }
+
+ outputFile, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_TRUNC, stat.Mode())
+ if err != nil {
+ return fmt.Errorf("failed to write output: %w", err)
+ }
+ defer outputFile.Close()
+
+ if _, err := fmt.Fprintf(outputFile, "%s\n", header); err != nil {
+ return fmt.Errorf("failed to write output: %w", err)
+ }
+
+ manifestGeneratorHash, err := os.Open(manifestGeneratorHashPath)
+ if err != nil {
+ return fmt.Errorf("failed to write output: %w", err)
+ }
+ defer manifestGeneratorHash.Close()
+
+ requirements, err := os.Open(requirementsPath)
+ if err != nil {
+ return fmt.Errorf("failed to write output: %w", err)
+ }
+ defer requirements.Close()
+
+ if err := manifestFile.Encode(outputFile, manifestGeneratorHash, requirements); err != nil {
+ return fmt.Errorf("failed to write output: %w", err)
+ }
+
+ return nil
+}
diff --git a/gazelle/manifest/hasher/BUILD.bazel b/gazelle/manifest/hasher/BUILD.bazel
new file mode 100644
index 0000000..2e7b125
--- /dev/null
+++ b/gazelle/manifest/hasher/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+ name = "hasher_lib",
+ srcs = ["main.go"],
+ importpath = "github.com/bazelbuild/rules_python/gazelle/manifest/hasher",
+ visibility = ["//visibility:private"],
+)
+
+go_binary(
+ name = "hasher",
+ embed = [":hasher_lib"],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//manifest:__pkg__"],
+)
diff --git a/gazelle/manifest/hasher/main.go b/gazelle/manifest/hasher/main.go
new file mode 100644
index 0000000..61f8952
--- /dev/null
+++ b/gazelle/manifest/hasher/main.go
@@ -0,0 +1,44 @@
+// 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.
+
+package main
+
+import (
+ "crypto/sha256"
+ "io"
+ "log"
+ "os"
+)
+
+func main() {
+ h := sha256.New()
+ out, err := os.Create(os.Args[1])
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer out.Close()
+ for _, filename := range os.Args[2:] {
+ f, err := os.Open(filename)
+ if err != nil {
+ log.Fatal(err)
+ }
+ defer f.Close()
+ if _, err := io.Copy(h, f); err != nil {
+ log.Fatal(err)
+ }
+ }
+ if _, err := out.Write(h.Sum(nil)); err != nil {
+ log.Fatal(err)
+ }
+}
diff --git a/gazelle/manifest/manifest.go b/gazelle/manifest/manifest.go
new file mode 100644
index 0000000..c49951d
--- /dev/null
+++ b/gazelle/manifest/manifest.go
@@ -0,0 +1,150 @@
+// 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.
+
+package manifest
+
+import (
+ "crypto/sha256"
+ "fmt"
+ "io"
+ "os"
+
+ "github.com/emirpasic/gods/sets/treeset"
+
+ yaml "gopkg.in/yaml.v2"
+)
+
+// File represents the gazelle_python.yaml file.
+type File struct {
+ Manifest *Manifest `yaml:"manifest,omitempty"`
+ // Integrity is the hash of the requirements.txt file and the Manifest for
+ // ensuring the integrity of the entire gazelle_python.yaml file. This
+ // controls the testing to keep the gazelle_python.yaml file up-to-date.
+ Integrity string `yaml:"integrity"`
+}
+
+// NewFile creates a new File with a given Manifest.
+func NewFile(manifest *Manifest) *File {
+ return &File{Manifest: manifest}
+}
+
+// Encode encodes the manifest file to the given writer.
+func (f *File) Encode(w io.Writer, manifestGeneratorHashFile, requirements io.Reader) error {
+ integrityBytes, err := f.calculateIntegrity(manifestGeneratorHashFile, requirements)
+ if err != nil {
+ return fmt.Errorf("failed to encode manifest file: %w", err)
+ }
+ f.Integrity = fmt.Sprintf("%x", integrityBytes)
+ encoder := yaml.NewEncoder(w)
+ defer encoder.Close()
+ if err := encoder.Encode(f); err != nil {
+ return fmt.Errorf("failed to encode manifest file: %w", err)
+ }
+ return nil
+}
+
+// VerifyIntegrity verifies if the integrity set in the File is valid.
+func (f *File) VerifyIntegrity(manifestGeneratorHashFile, requirements io.Reader) (bool, error) {
+ integrityBytes, err := f.calculateIntegrity(manifestGeneratorHashFile, requirements)
+ if err != nil {
+ return false, fmt.Errorf("failed to verify integrity: %w", err)
+ }
+ valid := (f.Integrity == fmt.Sprintf("%x", integrityBytes))
+ return valid, nil
+}
+
+// calculateIntegrity calculates the integrity of the manifest file based on the
+// provided checksum for the requirements.txt file used as input to the modules
+// mapping, plus the manifest structure in the manifest file. This integrity
+// calculation ensures the manifest files are kept up-to-date.
+func (f *File) calculateIntegrity(
+ manifestGeneratorHash, requirements io.Reader,
+) ([]byte, error) {
+ hash := sha256.New()
+
+ // Sum the manifest part of the file.
+ encoder := yaml.NewEncoder(hash)
+ defer encoder.Close()
+ if err := encoder.Encode(f.Manifest); err != nil {
+ return nil, fmt.Errorf("failed to calculate integrity: %w", err)
+ }
+
+ // Sum the manifest generator checksum bytes.
+ if _, err := io.Copy(hash, manifestGeneratorHash); err != nil {
+ return nil, fmt.Errorf("failed to calculate integrity: %w", err)
+ }
+
+ // Sum the requirements.txt checksum bytes.
+ if _, err := io.Copy(hash, requirements); err != nil {
+ return nil, fmt.Errorf("failed to calculate integrity: %w", err)
+ }
+
+ return hash.Sum(nil), nil
+}
+
+// Decode decodes the manifest file from the given path.
+func (f *File) Decode(manifestPath string) error {
+ file, err := os.Open(manifestPath)
+ if err != nil {
+ return fmt.Errorf("failed to decode manifest file: %w", err)
+ }
+ defer file.Close()
+
+ decoder := yaml.NewDecoder(file)
+ if err := decoder.Decode(f); err != nil {
+ return fmt.Errorf("failed to decode manifest file: %w", err)
+ }
+
+ return nil
+}
+
+// ModulesMapping is the type used to map from importable Python modules to
+// the wheel names that provide these modules.
+type ModulesMapping map[string]string
+
+// MarshalYAML makes sure that we sort the module names before marshaling
+// the contents of `ModulesMapping` to a YAML file. This ensures that the
+// file is deterministically generated from the map.
+func (m ModulesMapping) MarshalYAML() (interface{}, error) {
+ var mapslice yaml.MapSlice
+ keySet := treeset.NewWithStringComparator()
+ for key := range m {
+ keySet.Add(key)
+ }
+ for _, key := range keySet.Values() {
+ mapslice = append(mapslice, yaml.MapItem{Key: key, Value: m[key.(string)]})
+ }
+ return mapslice, nil
+}
+
+// Manifest represents the structure of the Gazelle manifest file.
+type Manifest struct {
+ // ModulesMapping is the mapping from importable modules to which Python
+ // wheel name provides these modules.
+ ModulesMapping ModulesMapping `yaml:"modules_mapping"`
+ // PipDepsRepositoryName is the name of the pip_install repository target.
+ // DEPRECATED
+ PipDepsRepositoryName string `yaml:"pip_deps_repository_name,omitempty"`
+ // PipRepository contains the information for pip_install or pip_repository
+ // target.
+ PipRepository *PipRepository `yaml:"pip_repository,omitempty"`
+}
+
+type PipRepository struct {
+ // The name of the pip_install or pip_repository target.
+ Name string
+ // UsePipRepositoryAliases allows to use aliases generated pip_repository
+ // when passing incompatible_generate_aliases = True.
+ UsePipRepositoryAliases bool `yaml:"use_pip_repository_aliases,omitempty"`
+}
diff --git a/gazelle/manifest/manifest_test.go b/gazelle/manifest/manifest_test.go
new file mode 100644
index 0000000..43c4099
--- /dev/null
+++ b/gazelle/manifest/manifest_test.go
@@ -0,0 +1,108 @@
+// 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.
+
+package manifest_test
+
+import (
+ "bytes"
+ "log"
+ "os"
+ "reflect"
+ "strings"
+ "testing"
+
+ "github.com/bazelbuild/rules_python/gazelle/manifest"
+)
+
+var modulesMapping = manifest.ModulesMapping{
+ "arrow": "arrow",
+ "arrow.__init__": "arrow",
+ "arrow.api": "arrow",
+ "arrow.arrow": "arrow",
+ "arrow.factory": "arrow",
+ "arrow.formatter": "arrow",
+ "arrow.locales": "arrow",
+ "arrow.parser": "arrow",
+ "arrow.util": "arrow",
+}
+
+const pipDepsRepositoryName = "test_repository_name"
+
+func TestFile(t *testing.T) {
+ t.Run("Encode", func(t *testing.T) {
+ f := manifest.NewFile(&manifest.Manifest{
+ ModulesMapping: modulesMapping,
+ PipDepsRepositoryName: pipDepsRepositoryName,
+ })
+ var b bytes.Buffer
+ manifestGeneratorHashFile := strings.NewReader("")
+ requirements, err := os.Open("testdata/requirements.txt")
+ if err != nil {
+ log.Println(err)
+ t.FailNow()
+ }
+ defer requirements.Close()
+ if err := f.Encode(&b, manifestGeneratorHashFile, requirements); err != nil {
+ log.Println(err)
+ t.FailNow()
+ }
+ expected, err := os.ReadFile("testdata/gazelle_python.yaml")
+ if err != nil {
+ log.Println(err)
+ t.FailNow()
+ }
+ if !bytes.Equal(expected, b.Bytes()) {
+ log.Printf("encoded manifest doesn't match expected output: %v\n", b.String())
+ t.FailNow()
+ }
+ })
+ t.Run("Decode", func(t *testing.T) {
+ f := manifest.NewFile(&manifest.Manifest{})
+ if err := f.Decode("testdata/gazelle_python.yaml"); err != nil {
+ log.Println(err)
+ t.FailNow()
+ }
+ if !reflect.DeepEqual(modulesMapping, f.Manifest.ModulesMapping) {
+ log.Println("decoded modules_mapping doesn't match expected value")
+ t.FailNow()
+ }
+ if f.Manifest.PipDepsRepositoryName != pipDepsRepositoryName {
+ log.Println("decoded pip repository name doesn't match expected value")
+ t.FailNow()
+ }
+ })
+ t.Run("VerifyIntegrity", func(t *testing.T) {
+ f := manifest.NewFile(&manifest.Manifest{})
+ if err := f.Decode("testdata/gazelle_python.yaml"); err != nil {
+ log.Println(err)
+ t.FailNow()
+ }
+ manifestGeneratorHashFile := strings.NewReader("")
+ requirements, err := os.Open("testdata/requirements.txt")
+ if err != nil {
+ log.Println(err)
+ t.FailNow()
+ }
+ defer requirements.Close()
+ valid, err := f.VerifyIntegrity(manifestGeneratorHashFile, requirements)
+ if err != nil {
+ log.Println(err)
+ t.FailNow()
+ }
+ if !valid {
+ log.Println("decoded manifest file is not valid")
+ t.FailNow()
+ }
+ })
+}
diff --git a/gazelle/manifest/test/BUILD.bazel b/gazelle/manifest/test/BUILD.bazel
new file mode 100644
index 0000000..28c6c54
--- /dev/null
+++ b/gazelle/manifest/test/BUILD.bazel
@@ -0,0 +1,9 @@
+# gazelle:ignore
+
+exports_files(["test.go"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//manifest:__pkg__"],
+)
diff --git a/gazelle/manifest/test/test.go b/gazelle/manifest/test/test.go
new file mode 100644
index 0000000..72cb260
--- /dev/null
+++ b/gazelle/manifest/test/test.go
@@ -0,0 +1,78 @@
+// 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.
+
+/*
+test.go is a unit test that asserts the Gazelle YAML manifest is up-to-date in
+regards to the requirements.txt.
+
+It re-hashes the requirements.txt and compares it to the recorded one in the
+existing generated Gazelle manifest.
+*/
+package test
+
+import (
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/bazelbuild/rules_python/gazelle/manifest"
+)
+
+func TestGazelleManifestIsUpdated(t *testing.T) {
+ requirementsPath := os.Getenv("_TEST_REQUIREMENTS")
+ if requirementsPath == "" {
+ t.Fatalf("_TEST_REQUIREMENTS must be set")
+ }
+
+ manifestPath := os.Getenv("_TEST_MANIFEST")
+ if manifestPath == "" {
+ t.Fatalf("_TEST_MANIFEST must be set")
+ }
+
+ manifestFile := new(manifest.File)
+ if err := manifestFile.Decode(manifestPath); err != nil {
+ t.Fatalf("decoding manifest file: %v", err)
+ }
+
+ if manifestFile.Integrity == "" {
+ t.Fatal("failed to find the Gazelle manifest file integrity")
+ }
+
+ manifestGeneratorHashPath := os.Getenv("_TEST_MANIFEST_GENERATOR_HASH")
+ manifestGeneratorHash, err := os.Open(manifestGeneratorHashPath)
+ if err != nil {
+ t.Fatalf("opening %q: %v", manifestGeneratorHashPath, err)
+ }
+ defer manifestGeneratorHash.Close()
+
+ requirements, err := os.Open(requirementsPath)
+ if err != nil {
+ t.Fatalf("opening %q: %v", requirementsPath, err)
+ }
+ defer requirements.Close()
+
+ valid, err := manifestFile.VerifyIntegrity(manifestGeneratorHash, requirements)
+ if err != nil {
+ t.Fatalf("verifying integrity: %v", err)
+ }
+ if !valid {
+ manifestRealpath, err := filepath.EvalSymlinks(manifestPath)
+ if err != nil {
+ t.Fatalf("evaluating symlink %q: %v", manifestPath, err)
+ }
+ t.Errorf(
+ "%q is out-of-date. Follow the update instructions in that file to resolve this",
+ manifestRealpath)
+ }
+}
diff --git a/gazelle/manifest/testdata/gazelle_python.yaml b/gazelle/manifest/testdata/gazelle_python.yaml
new file mode 100644
index 0000000..70f7aff
--- /dev/null
+++ b/gazelle/manifest/testdata/gazelle_python.yaml
@@ -0,0 +1,13 @@
+manifest:
+ modules_mapping:
+ arrow: arrow
+ arrow.__init__: arrow
+ arrow.api: arrow
+ arrow.arrow: arrow
+ arrow.factory: arrow
+ arrow.formatter: arrow
+ arrow.locales: arrow
+ arrow.parser: arrow
+ arrow.util: arrow
+ pip_deps_repository_name: test_repository_name
+integrity: eedf187f8b7ec27cdfc682feee4206e063b51d13d78f77c05d3a30ec11bd7411
diff --git a/gazelle/manifest/testdata/requirements.txt b/gazelle/manifest/testdata/requirements.txt
new file mode 100644
index 0000000..9dd49a6
--- /dev/null
+++ b/gazelle/manifest/testdata/requirements.txt
@@ -0,0 +1,3 @@
+# This is a file for testing only.
+
+arrow==0.12.1 \ No newline at end of file
diff --git a/gazelle/modules_mapping/BUILD.bazel b/gazelle/modules_mapping/BUILD.bazel
new file mode 100644
index 0000000..1855551
--- /dev/null
+++ b/gazelle/modules_mapping/BUILD.bazel
@@ -0,0 +1,13 @@
+load("@rules_python//python:defs.bzl", "py_binary")
+
+py_binary(
+ name = "generator",
+ srcs = ["generator.py"],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//:__pkg__"],
+)
diff --git a/gazelle/modules_mapping/def.bzl b/gazelle/modules_mapping/def.bzl
new file mode 100644
index 0000000..54fc8ad
--- /dev/null
+++ b/gazelle/modules_mapping/def.bzl
@@ -0,0 +1,66 @@
+# 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.
+
+"""Definitions for the modules_mapping.json generation.
+
+The modules_mapping.json file is a mapping from Python modules to the wheel
+names that provide those modules. It is used for determining which wheel
+distribution should be used in the `deps` attribute of `py_*` targets.
+
+This mapping is necessary when reading Python import statements and determining
+if they are provided by third-party dependencies. Most importantly, when the
+module name doesn't match the wheel distribution name.
+"""
+
+def _modules_mapping_impl(ctx):
+ modules_mapping = ctx.actions.declare_file(ctx.attr.modules_mapping_name)
+ args = ctx.actions.args()
+ args.add("--output_file", modules_mapping.path)
+ args.add_all("--exclude_patterns", ctx.attr.exclude_patterns)
+ args.add_all("--wheels", [whl.path for whl in ctx.files.wheels])
+ ctx.actions.run(
+ inputs = ctx.files.wheels,
+ outputs = [modules_mapping],
+ executable = ctx.executable._generator,
+ arguments = [args],
+ use_default_shell_env = False,
+ )
+ return [DefaultInfo(files = depset([modules_mapping]))]
+
+modules_mapping = rule(
+ _modules_mapping_impl,
+ attrs = {
+ "exclude_patterns": attr.string_list(
+ default = ["^_|(\\._)+"],
+ doc = "A set of regex patterns to match against each calculated module path. By default, exclude the modules starting with underscores.",
+ mandatory = False,
+ ),
+ "modules_mapping_name": attr.string(
+ default = "modules_mapping.json",
+ doc = "The name for the output JSON file.",
+ mandatory = False,
+ ),
+ "wheels": attr.label_list(
+ allow_files = True,
+ doc = "The list of wheels, usually the 'all_whl_requirements' from @<pip_repository>//:requirements.bzl",
+ mandatory = True,
+ ),
+ "_generator": attr.label(
+ cfg = "exec",
+ default = "//modules_mapping:generator",
+ executable = True,
+ ),
+ },
+ doc = "Creates a modules_mapping.json file for mapping module names to wheel distribution names.",
+)
diff --git a/gazelle/modules_mapping/generator.py b/gazelle/modules_mapping/generator.py
new file mode 100644
index 0000000..be57eac
--- /dev/null
+++ b/gazelle/modules_mapping/generator.py
@@ -0,0 +1,133 @@
+# 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.
+
+import argparse
+import json
+import pathlib
+import re
+import sys
+import zipfile
+
+
+# Generator is the modules_mapping.json file generator.
+class Generator:
+ stderr = None
+ output_file = None
+ excluded_patterns = None
+ mapping = {}
+
+ def __init__(self, stderr, output_file, excluded_patterns):
+ self.stderr = stderr
+ self.output_file = output_file
+ self.excluded_patterns = [re.compile(pattern) for pattern in excluded_patterns]
+
+ # dig_wheel analyses the wheel .whl file determining the modules it provides
+ # by looking at the directory structure.
+ def dig_wheel(self, whl):
+ with zipfile.ZipFile(whl, "r") as zip_file:
+ for path in zip_file.namelist():
+ if is_metadata(path):
+ if data_has_purelib_or_platlib(path):
+ self.module_for_path(path, whl)
+ else:
+ continue
+ else:
+ self.module_for_path(path, whl)
+
+ def module_for_path(self, path, whl):
+ ext = pathlib.Path(path).suffix
+ if ext == ".py" or ext == ".so":
+ if "purelib" in path or "platlib" in path:
+ root = "/".join(path.split("/")[2:])
+ else:
+ root = path
+
+ wheel_name = get_wheel_name(whl)
+
+ if root.endswith("/__init__.py"):
+ # Note the '/' here means that the __init__.py is not in the
+ # root of the wheel, therefore we can index the directory
+ # where this file is as an importable package.
+ module = root[: -len("/__init__.py")].replace("/", ".")
+ if not self.is_excluded(module):
+ self.mapping[module] = wheel_name
+
+ # Always index the module file.
+ if ext == ".so":
+ # Also remove extra metadata that is embeded as part of
+ # the file name as an extra extension.
+ ext = "".join(pathlib.Path(root).suffixes)
+ module = root[: -len(ext)].replace("/", ".")
+ if not self.is_excluded(module):
+ self.mapping[module] = wheel_name
+
+ def is_excluded(self, module):
+ for pattern in self.excluded_patterns:
+ if pattern.search(module):
+ return True
+ return False
+
+ # run is the entrypoint for the generator.
+ def run(self, wheels):
+ for whl in wheels:
+ try:
+ self.dig_wheel(whl)
+ except AssertionError as error:
+ print(error, file=self.stderr)
+ return 1
+ mapping_json = json.dumps(self.mapping)
+ with open(self.output_file, "w") as f:
+ f.write(mapping_json)
+ return 0
+
+
+def get_wheel_name(path):
+ pp = pathlib.PurePath(path)
+ if pp.suffix != ".whl":
+ raise RuntimeError(
+ "{} is not a valid wheel file name: the wheel doesn't follow ".format(
+ pp.name
+ )
+ + "https://www.python.org/dev/peps/pep-0427/#file-name-convention"
+ )
+ return pp.name[: pp.name.find("-")]
+
+
+# is_metadata checks if the path is in a metadata directory.
+# Ref: https://www.python.org/dev/peps/pep-0427/#file-contents.
+def is_metadata(path):
+ top_level = path.split("/")[0].lower()
+ return top_level.endswith(".dist-info") or top_level.endswith(".data")
+
+
+# The .data is allowed to contain a full purelib or platlib directory
+# These get unpacked into site-packages, so require indexing too.
+# This is the same if "Root-Is-Purelib: true" is set and the files are at the root.
+# Ref: https://peps.python.org/pep-0427/#what-s-the-deal-with-purelib-vs-platlib
+def data_has_purelib_or_platlib(path):
+ maybe_lib = path.split("/")[1].lower()
+ return is_metadata(path) and (maybe_lib == "purelib" or maybe_lib == "platlib")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(
+ prog="generator",
+ description="Generates the modules mapping used by the Gazelle manifest.",
+ )
+ parser.add_argument("--output_file", type=str)
+ parser.add_argument("--exclude_patterns", nargs="+", default=[])
+ parser.add_argument("--wheels", nargs="+", default=[])
+ args = parser.parse_args()
+ generator = Generator(sys.stderr, args.output_file, args.exclude_patterns)
+ exit(generator.run(args.wheels))
diff --git a/gazelle/python/BUILD.bazel b/gazelle/python/BUILD.bazel
new file mode 100644
index 0000000..fcfe81b
--- /dev/null
+++ b/gazelle/python/BUILD.bazel
@@ -0,0 +1,80 @@
+load("@bazel_gazelle//:def.bzl", "gazelle_binary")
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+load("@rules_python//python:defs.bzl", "py_binary")
+
+go_library(
+ name = "python",
+ srcs = [
+ "configure.go",
+ "fix.go",
+ "generate.go",
+ "kinds.go",
+ "language.go",
+ "lifecycle.go",
+ "parser.go",
+ "resolve.go",
+ "std_modules.go",
+ "target.go",
+ ],
+ data = [
+ ":parse",
+ ":std_modules",
+ ],
+ importpath = "github.com/bazelbuild/rules_python/gazelle/python",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//manifest",
+ "//pythonconfig",
+ "@bazel_gazelle//config:go_default_library",
+ "@bazel_gazelle//label:go_default_library",
+ "@bazel_gazelle//language:go_default_library",
+ "@bazel_gazelle//repo:go_default_library",
+ "@bazel_gazelle//resolve:go_default_library",
+ "@bazel_gazelle//rule:go_default_library",
+ "@com_github_bazelbuild_buildtools//build:go_default_library",
+ "@com_github_bmatcuk_doublestar//:doublestar",
+ "@com_github_emirpasic_gods//lists/singlylinkedlist",
+ "@com_github_emirpasic_gods//sets/treeset",
+ "@com_github_emirpasic_gods//utils",
+ "@io_bazel_rules_go//go/tools/bazel:go_default_library",
+ ],
+)
+
+py_binary(
+ name = "parse",
+ srcs = ["parse.py"],
+ visibility = ["//visibility:public"],
+)
+
+py_binary(
+ name = "std_modules",
+ srcs = ["std_modules.py"],
+ visibility = ["//visibility:public"],
+)
+
+go_test(
+ name = "python_test",
+ srcs = ["python_test.go"],
+ data = [
+ ":gazelle_binary",
+ ":parse",
+ ":std_modules",
+ ] + glob(["testdata/**"]),
+ deps = [
+ "@bazel_gazelle//testtools:go_default_library",
+ "@com_github_ghodss_yaml//:yaml",
+ "@io_bazel_rules_go//go/tools/bazel:go_default_library",
+ ],
+)
+
+gazelle_binary(
+ name = "gazelle_binary",
+ languages = [":python"],
+ visibility = ["//visibility:public"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//:__pkg__"],
+)
diff --git a/gazelle/python/configure.go b/gazelle/python/configure.go
new file mode 100644
index 0000000..32f9ab0
--- /dev/null
+++ b/gazelle/python/configure.go
@@ -0,0 +1,178 @@
+// 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.
+
+package python
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "strconv"
+ "strings"
+
+ "github.com/bazelbuild/bazel-gazelle/config"
+ "github.com/bazelbuild/bazel-gazelle/rule"
+
+ "github.com/bazelbuild/rules_python/gazelle/manifest"
+ "github.com/bazelbuild/rules_python/gazelle/pythonconfig"
+)
+
+// Configurer satisfies the config.Configurer interface. It's the
+// language-specific configuration extension.
+type Configurer struct{}
+
+// RegisterFlags registers command-line flags used by the extension. This
+// method is called once with the root configuration when Gazelle
+// starts. RegisterFlags may set an initial values in Config.Exts. When flags
+// are set, they should modify these values.
+func (py *Configurer) RegisterFlags(fs *flag.FlagSet, cmd string, c *config.Config) {}
+
+// CheckFlags validates the configuration after command line flags are parsed.
+// This is called once with the root configuration when Gazelle starts.
+// CheckFlags may set default values in flags or make implied changes.
+func (py *Configurer) CheckFlags(fs *flag.FlagSet, c *config.Config) error {
+ return nil
+}
+
+// KnownDirectives returns a list of directive keys that this Configurer can
+// interpret. Gazelle prints errors for directives that are not recoginized by
+// any Configurer.
+func (py *Configurer) KnownDirectives() []string {
+ return []string{
+ pythonconfig.PythonExtensionDirective,
+ pythonconfig.PythonRootDirective,
+ pythonconfig.PythonManifestFileNameDirective,
+ pythonconfig.IgnoreFilesDirective,
+ pythonconfig.IgnoreDependenciesDirective,
+ pythonconfig.ValidateImportStatementsDirective,
+ pythonconfig.GenerationMode,
+ pythonconfig.LibraryNamingConvention,
+ pythonconfig.BinaryNamingConvention,
+ pythonconfig.TestNamingConvention,
+ }
+}
+
+// Configure modifies the configuration using directives and other information
+// extracted from a build file. Configure is called in each directory.
+//
+// c is the configuration for the current directory. It starts out as a copy
+// of the configuration for the parent directory.
+//
+// rel is the slash-separated relative path from the repository root to
+// the current directory. It is "" for the root directory itself.
+//
+// f is the build file for the current directory or nil if there is no
+// existing build file.
+func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
+ // Create the root config.
+ if _, exists := c.Exts[languageName]; !exists {
+ rootConfig := pythonconfig.New(c.RepoRoot, "")
+ c.Exts[languageName] = pythonconfig.Configs{"": rootConfig}
+ }
+
+ configs := c.Exts[languageName].(pythonconfig.Configs)
+
+ config, exists := configs[rel]
+ if !exists {
+ parent := configs.ParentForPackage(rel)
+ config = parent.NewChild()
+ configs[rel] = config
+ }
+
+ if f == nil {
+ return
+ }
+
+ gazelleManifestFilename := "gazelle_python.yaml"
+
+ for _, d := range f.Directives {
+ switch d.Key {
+ case "exclude":
+ // We record the exclude directive for coarse-grained packages
+ // since we do manual tree traversal in this mode.
+ config.AddExcludedPattern(filepath.Join(rel, strings.TrimSpace(d.Value)))
+ case pythonconfig.PythonExtensionDirective:
+ switch d.Value {
+ case "enabled":
+ config.SetExtensionEnabled(true)
+ case "disabled":
+ config.SetExtensionEnabled(false)
+ default:
+ err := fmt.Errorf("invalid value for directive %q: %s: possible values are enabled/disabled",
+ pythonconfig.PythonExtensionDirective, d.Value)
+ log.Fatal(err)
+ }
+ case pythonconfig.PythonRootDirective:
+ config.SetPythonProjectRoot(rel)
+ case pythonconfig.PythonManifestFileNameDirective:
+ gazelleManifestFilename = strings.TrimSpace(d.Value)
+ case pythonconfig.IgnoreFilesDirective:
+ for _, ignoreFile := range strings.Split(d.Value, ",") {
+ config.AddIgnoreFile(ignoreFile)
+ }
+ case pythonconfig.IgnoreDependenciesDirective:
+ for _, ignoreDependency := range strings.Split(d.Value, ",") {
+ config.AddIgnoreDependency(ignoreDependency)
+ }
+ case pythonconfig.ValidateImportStatementsDirective:
+ v, err := strconv.ParseBool(strings.TrimSpace(d.Value))
+ if err != nil {
+ log.Fatal(err)
+ }
+ config.SetValidateImportStatements(v)
+ case pythonconfig.GenerationMode:
+ switch pythonconfig.GenerationModeType(strings.TrimSpace(d.Value)) {
+ case pythonconfig.GenerationModePackage:
+ config.SetCoarseGrainedGeneration(false)
+ case pythonconfig.GenerationModeProject:
+ config.SetCoarseGrainedGeneration(true)
+ default:
+ err := fmt.Errorf("invalid value for directive %q: %s",
+ pythonconfig.GenerationMode, d.Value)
+ log.Fatal(err)
+ }
+ case pythonconfig.LibraryNamingConvention:
+ config.SetLibraryNamingConvention(strings.TrimSpace(d.Value))
+ case pythonconfig.BinaryNamingConvention:
+ config.SetBinaryNamingConvention(strings.TrimSpace(d.Value))
+ case pythonconfig.TestNamingConvention:
+ config.SetTestNamingConvention(strings.TrimSpace(d.Value))
+ }
+ }
+
+ gazelleManifestPath := filepath.Join(c.RepoRoot, rel, gazelleManifestFilename)
+ gazelleManifest, err := py.loadGazelleManifest(gazelleManifestPath)
+ if err != nil {
+ log.Fatal(err)
+ }
+ if gazelleManifest != nil {
+ config.SetGazelleManifest(gazelleManifest)
+ }
+}
+
+func (py *Configurer) loadGazelleManifest(gazelleManifestPath string) (*manifest.Manifest, error) {
+ if _, err := os.Stat(gazelleManifestPath); err != nil {
+ if os.IsNotExist(err) {
+ return nil, nil
+ }
+ return nil, fmt.Errorf("failed to load Gazelle manifest at %q: %w", gazelleManifestPath, err)
+ }
+ manifestFile := new(manifest.File)
+ if err := manifestFile.Decode(gazelleManifestPath); err != nil {
+ return nil, fmt.Errorf("failed to load Gazelle manifest at %q: %w", gazelleManifestPath, err)
+ }
+ return manifestFile.Manifest, nil
+}
diff --git a/gazelle/python/fix.go b/gazelle/python/fix.go
new file mode 100644
index 0000000..1ca4257
--- /dev/null
+++ b/gazelle/python/fix.go
@@ -0,0 +1,27 @@
+// 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.
+
+package python
+
+import (
+ "github.com/bazelbuild/bazel-gazelle/config"
+ "github.com/bazelbuild/bazel-gazelle/rule"
+)
+
+// Fix repairs deprecated usage of language-specific rules in f. This is
+// called before the file is indexed. Unless c.ShouldFix is true, fixes
+// that delete or rename rules should not be performed.
+func (py *Python) Fix(c *config.Config, f *rule.File) {
+ // TODO(f0rmiga): implement.
+}
diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go
new file mode 100644
index 0000000..fb41324
--- /dev/null
+++ b/gazelle/python/generate.go
@@ -0,0 +1,444 @@
+// 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.
+
+package python
+
+import (
+ "fmt"
+ "io/fs"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/bazelbuild/bazel-gazelle/config"
+ "github.com/bazelbuild/bazel-gazelle/label"
+ "github.com/bazelbuild/bazel-gazelle/language"
+ "github.com/bazelbuild/bazel-gazelle/rule"
+ "github.com/bazelbuild/rules_python/gazelle/pythonconfig"
+ "github.com/bmatcuk/doublestar"
+ "github.com/emirpasic/gods/lists/singlylinkedlist"
+ "github.com/emirpasic/gods/sets/treeset"
+ godsutils "github.com/emirpasic/gods/utils"
+)
+
+const (
+ pyLibraryEntrypointFilename = "__init__.py"
+ pyBinaryEntrypointFilename = "__main__.py"
+ pyTestEntrypointFilename = "__test__.py"
+ pyTestEntrypointTargetname = "__test__"
+ conftestFilename = "conftest.py"
+ conftestTargetname = "conftest"
+)
+
+var (
+ buildFilenames = []string{"BUILD", "BUILD.bazel"}
+)
+
+func GetActualKindName(kind string, args language.GenerateArgs) string {
+ if kindOverride, ok := args.Config.KindMap[kind]; ok {
+ return kindOverride.KindName
+ }
+ return kind
+}
+
+// GenerateRules extracts build metadata from source files in a directory.
+// GenerateRules is called in each directory where an update is requested
+// in depth-first post-order.
+func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateResult {
+ cfgs := args.Config.Exts[languageName].(pythonconfig.Configs)
+ cfg := cfgs[args.Rel]
+
+ if !cfg.ExtensionEnabled() {
+ return language.GenerateResult{}
+ }
+
+ if !isBazelPackage(args.Dir) {
+ if cfg.CoarseGrainedGeneration() {
+ // Determine if the current directory is the root of the coarse-grained
+ // generation. If not, return without generating anything.
+ parent := cfg.Parent()
+ if parent != nil && parent.CoarseGrainedGeneration() {
+ return language.GenerateResult{}
+ }
+ } else if !hasEntrypointFile(args.Dir) {
+ return language.GenerateResult{}
+ }
+ }
+
+ actualPyBinaryKind := GetActualKindName(pyBinaryKind, args)
+ actualPyLibraryKind := GetActualKindName(pyLibraryKind, args)
+ actualPyTestKind := GetActualKindName(pyTestKind, args)
+
+ pythonProjectRoot := cfg.PythonProjectRoot()
+
+ packageName := filepath.Base(args.Dir)
+
+ pyLibraryFilenames := treeset.NewWith(godsutils.StringComparator)
+ pyTestFilenames := treeset.NewWith(godsutils.StringComparator)
+ pyFileNames := treeset.NewWith(godsutils.StringComparator)
+
+ // hasPyBinary controls whether a py_binary target should be generated for
+ // this package or not.
+ hasPyBinary := false
+
+ // hasPyTestEntryPointFile and hasPyTestEntryPointTarget control whether a py_test target should
+ // be generated for this package or not.
+ hasPyTestEntryPointFile := false
+ hasPyTestEntryPointTarget := false
+ hasConftestFile := false
+
+ for _, f := range args.RegularFiles {
+ if cfg.IgnoresFile(filepath.Base(f)) {
+ continue
+ }
+ ext := filepath.Ext(f)
+ if ext == ".py" {
+ pyFileNames.Add(f)
+ if !hasPyBinary && f == pyBinaryEntrypointFilename {
+ hasPyBinary = true
+ } else if !hasPyTestEntryPointFile && f == pyTestEntrypointFilename {
+ hasPyTestEntryPointFile = true
+ } else if f == conftestFilename {
+ hasConftestFile = true
+ } else if strings.HasSuffix(f, "_test.py") || strings.HasPrefix(f, "test_") {
+ pyTestFilenames.Add(f)
+ } else {
+ pyLibraryFilenames.Add(f)
+ }
+ }
+ }
+
+ // If a __test__.py file was not found on disk, search for targets that are
+ // named __test__.
+ if !hasPyTestEntryPointFile && args.File != nil {
+ for _, rule := range args.File.Rules {
+ if rule.Name() == pyTestEntrypointTargetname {
+ hasPyTestEntryPointTarget = true
+ break
+ }
+ }
+ }
+
+ // Add files from subdirectories if they meet the criteria.
+ for _, d := range args.Subdirs {
+ // boundaryPackages represents child Bazel packages that are used as a
+ // boundary to stop processing under that tree.
+ boundaryPackages := make(map[string]struct{})
+ err := filepath.WalkDir(
+ filepath.Join(args.Dir, d),
+ func(path string, entry fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ // Ignore the path if it crosses any boundary package. Walking
+ // the tree is still important because subsequent paths can
+ // represent files that have not crossed any boundaries.
+ for bp := range boundaryPackages {
+ if strings.HasPrefix(path, bp) {
+ return nil
+ }
+ }
+ if entry.IsDir() {
+ // If we are visiting a directory, we determine if we should
+ // halt digging the tree based on a few criterias:
+ // 1. The directory has a BUILD or BUILD.bazel files. Then
+ // it doesn't matter at all what it has since it's a
+ // separate Bazel package.
+ // 2. (only for fine-grained generation) The directory has
+ // an __init__.py, __main__.py or __test__.py, meaning
+ // a BUILD file will be generated.
+ if isBazelPackage(path) {
+ boundaryPackages[path] = struct{}{}
+ return nil
+ }
+
+ if !cfg.CoarseGrainedGeneration() && hasEntrypointFile(path) {
+ return fs.SkipDir
+ }
+
+ return nil
+ }
+ if filepath.Ext(path) == ".py" {
+ if cfg.CoarseGrainedGeneration() || !isEntrypointFile(path) {
+ srcPath, _ := filepath.Rel(args.Dir, path)
+ repoPath := filepath.Join(args.Rel, srcPath)
+ excludedPatterns := cfg.ExcludedPatterns()
+ if excludedPatterns != nil {
+ it := excludedPatterns.Iterator()
+ for it.Next() {
+ excludedPattern := it.Value().(string)
+ isExcluded, err := doublestar.Match(excludedPattern, repoPath)
+ if err != nil {
+ return err
+ }
+ if isExcluded {
+ return nil
+ }
+ }
+ }
+ baseName := filepath.Base(path)
+ if strings.HasSuffix(baseName, "_test.py") || strings.HasPrefix(baseName, "test_") {
+ pyTestFilenames.Add(srcPath)
+ } else {
+ pyLibraryFilenames.Add(srcPath)
+ }
+ }
+ }
+ return nil
+ },
+ )
+ if err != nil {
+ log.Printf("ERROR: %v\n", err)
+ return language.GenerateResult{}
+ }
+ }
+
+ parser := newPython3Parser(args.Config.RepoRoot, args.Rel, cfg.IgnoresDependency)
+ visibility := fmt.Sprintf("//%s:__subpackages__", pythonProjectRoot)
+
+ var result language.GenerateResult
+ result.Gen = make([]*rule.Rule, 0)
+
+ collisionErrors := singlylinkedlist.New()
+
+ var pyLibrary *rule.Rule
+ if !pyLibraryFilenames.Empty() {
+ deps, err := parser.parse(pyLibraryFilenames)
+ if err != nil {
+ log.Fatalf("ERROR: %v\n", err)
+ }
+
+ pyLibraryTargetName := cfg.RenderLibraryName(packageName)
+
+ // Check if a target with the same name we are generating already
+ // exists, and if it is of a different kind from the one we are
+ // generating. If so, we have to throw an error since Gazelle won't
+ // generate it correctly.
+ if args.File != nil {
+ for _, t := range args.File.Rules {
+ if t.Name() == pyLibraryTargetName && t.Kind() != actualPyLibraryKind {
+ fqTarget := label.New("", args.Rel, pyLibraryTargetName)
+ err := fmt.Errorf("failed to generate target %q of kind %q: "+
+ "a target of kind %q with the same name already exists. "+
+ "Use the '# gazelle:%s' directive to change the naming convention.",
+ fqTarget.String(), actualPyLibraryKind, t.Kind(), pythonconfig.LibraryNamingConvention)
+ collisionErrors.Add(err)
+ }
+ }
+ }
+
+ pyLibrary = newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
+ addVisibility(visibility).
+ addSrcs(pyLibraryFilenames).
+ addModuleDependencies(deps).
+ generateImportsAttribute().
+ build()
+
+ result.Gen = append(result.Gen, pyLibrary)
+ result.Imports = append(result.Imports, pyLibrary.PrivateAttr(config.GazelleImportsKey))
+ }
+
+ if hasPyBinary {
+ deps, err := parser.parseSingle(pyBinaryEntrypointFilename)
+ if err != nil {
+ log.Fatalf("ERROR: %v\n", err)
+ }
+
+ pyBinaryTargetName := cfg.RenderBinaryName(packageName)
+
+ // Check if a target with the same name we are generating already
+ // exists, and if it is of a different kind from the one we are
+ // generating. If so, we have to throw an error since Gazelle won't
+ // generate it correctly.
+ if args.File != nil {
+ for _, t := range args.File.Rules {
+ if t.Name() == pyBinaryTargetName && t.Kind() != actualPyBinaryKind {
+ fqTarget := label.New("", args.Rel, pyBinaryTargetName)
+ err := fmt.Errorf("failed to generate target %q of kind %q: "+
+ "a target of kind %q with the same name already exists. "+
+ "Use the '# gazelle:%s' directive to change the naming convention.",
+ fqTarget.String(), actualPyBinaryKind, t.Kind(), pythonconfig.BinaryNamingConvention)
+ collisionErrors.Add(err)
+ }
+ }
+ }
+
+ pyBinaryTarget := newTargetBuilder(pyBinaryKind, pyBinaryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
+ setMain(pyBinaryEntrypointFilename).
+ addVisibility(visibility).
+ addSrc(pyBinaryEntrypointFilename).
+ addModuleDependencies(deps).
+ generateImportsAttribute()
+
+ pyBinary := pyBinaryTarget.build()
+
+ result.Gen = append(result.Gen, pyBinary)
+ result.Imports = append(result.Imports, pyBinary.PrivateAttr(config.GazelleImportsKey))
+ }
+
+ var conftest *rule.Rule
+ if hasConftestFile {
+ deps, err := parser.parseSingle(conftestFilename)
+ if err != nil {
+ log.Fatalf("ERROR: %v\n", err)
+ }
+
+ // Check if a target with the same name we are generating already
+ // exists, and if it is of a different kind from the one we are
+ // generating. If so, we have to throw an error since Gazelle won't
+ // generate it correctly.
+ if args.File != nil {
+ for _, t := range args.File.Rules {
+ if t.Name() == conftestTargetname && t.Kind() != actualPyLibraryKind {
+ fqTarget := label.New("", args.Rel, conftestTargetname)
+ err := fmt.Errorf("failed to generate target %q of kind %q: "+
+ "a target of kind %q with the same name already exists.",
+ fqTarget.String(), actualPyLibraryKind, t.Kind())
+ collisionErrors.Add(err)
+ }
+ }
+ }
+
+ conftestTarget := newTargetBuilder(pyLibraryKind, conftestTargetname, pythonProjectRoot, args.Rel, pyFileNames).
+ addSrc(conftestFilename).
+ addModuleDependencies(deps).
+ addVisibility(visibility).
+ setTestonly().
+ generateImportsAttribute()
+
+ conftest = conftestTarget.build()
+
+ result.Gen = append(result.Gen, conftest)
+ result.Imports = append(result.Imports, conftest.PrivateAttr(config.GazelleImportsKey))
+ }
+
+ var pyTestTargets []*targetBuilder
+ newPyTestTargetBuilder := func(srcs *treeset.Set, pyTestTargetName string) *targetBuilder {
+ deps, err := parser.parse(srcs)
+ if err != nil {
+ log.Fatalf("ERROR: %v\n", err)
+ }
+ // Check if a target with the same name we are generating already
+ // exists, and if it is of a different kind from the one we are
+ // generating. If so, we have to throw an error since Gazelle won't
+ // generate it correctly.
+ if args.File != nil {
+ for _, t := range args.File.Rules {
+ if t.Name() == pyTestTargetName && t.Kind() != actualPyTestKind {
+ fqTarget := label.New("", args.Rel, pyTestTargetName)
+ err := fmt.Errorf("failed to generate target %q of kind %q: "+
+ "a target of kind %q with the same name already exists. "+
+ "Use the '# gazelle:%s' directive to change the naming convention.",
+ fqTarget.String(), actualPyTestKind, t.Kind(), pythonconfig.TestNamingConvention)
+ collisionErrors.Add(err)
+ }
+ }
+ }
+ return newTargetBuilder(pyTestKind, pyTestTargetName, pythonProjectRoot, args.Rel, pyFileNames).
+ addSrcs(srcs).
+ addModuleDependencies(deps).
+ generateImportsAttribute()
+ }
+ if hasPyTestEntryPointFile || hasPyTestEntryPointTarget {
+ if hasPyTestEntryPointFile {
+ // Only add the pyTestEntrypointFilename to the pyTestFilenames if
+ // the file exists on disk.
+ pyTestFilenames.Add(pyTestEntrypointFilename)
+ }
+ pyTestTargetName := cfg.RenderTestName(packageName)
+ pyTestTarget := newPyTestTargetBuilder(pyTestFilenames, pyTestTargetName)
+
+ if hasPyTestEntryPointTarget {
+ entrypointTarget := fmt.Sprintf(":%s", pyTestEntrypointTargetname)
+ main := fmt.Sprintf(":%s", pyTestEntrypointFilename)
+ pyTestTarget.
+ addSrc(entrypointTarget).
+ addResolvedDependency(entrypointTarget).
+ setMain(main)
+ } else {
+ pyTestTarget.setMain(pyTestEntrypointFilename)
+ }
+ pyTestTargets = append(pyTestTargets, pyTestTarget)
+ } else {
+ // Create one py_test target per file
+ pyTestFilenames.Each(func(index int, testFile interface{}) {
+ srcs := treeset.NewWith(godsutils.StringComparator, testFile)
+ pyTestTargetName := strings.TrimSuffix(filepath.Base(testFile.(string)), ".py")
+ pyTestTargets = append(pyTestTargets, newPyTestTargetBuilder(srcs, pyTestTargetName))
+ })
+ }
+
+ for _, pyTestTarget := range pyTestTargets {
+ if conftest != nil {
+ pyTestTarget.addModuleDependency(module{Name: strings.TrimSuffix(conftestFilename, ".py")})
+ }
+ pyTest := pyTestTarget.build()
+
+ result.Gen = append(result.Gen, pyTest)
+ result.Imports = append(result.Imports, pyTest.PrivateAttr(config.GazelleImportsKey))
+ }
+
+ if !collisionErrors.Empty() {
+ it := collisionErrors.Iterator()
+ for it.Next() {
+ log.Printf("ERROR: %v\n", it.Value())
+ }
+ os.Exit(1)
+ }
+
+ return result
+}
+
+// isBazelPackage determines if the directory is a Bazel package by probing for
+// the existence of a known BUILD file name.
+func isBazelPackage(dir string) bool {
+ for _, buildFilename := range buildFilenames {
+ path := filepath.Join(dir, buildFilename)
+ if _, err := os.Stat(path); err == nil {
+ return true
+ }
+ }
+ return false
+}
+
+// hasEntrypointFile determines if the directory has any of the established
+// entrypoint filenames.
+func hasEntrypointFile(dir string) bool {
+ for _, entrypointFilename := range []string{
+ pyLibraryEntrypointFilename,
+ pyBinaryEntrypointFilename,
+ pyTestEntrypointFilename,
+ } {
+ path := filepath.Join(dir, entrypointFilename)
+ if _, err := os.Stat(path); err == nil {
+ return true
+ }
+ }
+ return false
+}
+
+// isEntrypointFile returns whether the given path is an entrypoint file. The
+// given path can be absolute or relative.
+func isEntrypointFile(path string) bool {
+ basePath := filepath.Base(path)
+ switch basePath {
+ case pyLibraryEntrypointFilename,
+ pyBinaryEntrypointFilename,
+ pyTestEntrypointFilename:
+ return true
+ default:
+ return false
+ }
+}
diff --git a/gazelle/python/kinds.go b/gazelle/python/kinds.go
new file mode 100644
index 0000000..ab1afb7
--- /dev/null
+++ b/gazelle/python/kinds.go
@@ -0,0 +1,102 @@
+// 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.
+
+package python
+
+import (
+ "github.com/bazelbuild/bazel-gazelle/rule"
+)
+
+const (
+ pyBinaryKind = "py_binary"
+ pyLibraryKind = "py_library"
+ pyTestKind = "py_test"
+)
+
+// Kinds returns a map that maps rule names (kinds) and information on how to
+// match and merge attributes that may be found in rules of those kinds.
+func (*Python) Kinds() map[string]rule.KindInfo {
+ return pyKinds
+}
+
+var pyKinds = map[string]rule.KindInfo{
+ pyBinaryKind: {
+ MatchAny: true,
+ NonEmptyAttrs: map[string]bool{
+ "deps": true,
+ "main": true,
+ "srcs": true,
+ "imports": true,
+ "visibility": true,
+ },
+ SubstituteAttrs: map[string]bool{},
+ MergeableAttrs: map[string]bool{
+ "srcs": true,
+ },
+ ResolveAttrs: map[string]bool{
+ "deps": true,
+ },
+ },
+ pyLibraryKind: {
+ MatchAny: true,
+ NonEmptyAttrs: map[string]bool{
+ "deps": true,
+ "srcs": true,
+ "imports": true,
+ "visibility": true,
+ },
+ SubstituteAttrs: map[string]bool{},
+ MergeableAttrs: map[string]bool{
+ "srcs": true,
+ },
+ ResolveAttrs: map[string]bool{
+ "deps": true,
+ },
+ },
+ pyTestKind: {
+ MatchAny: false,
+ NonEmptyAttrs: map[string]bool{
+ "deps": true,
+ "main": true,
+ "srcs": true,
+ "imports": true,
+ "visibility": true,
+ },
+ SubstituteAttrs: map[string]bool{},
+ MergeableAttrs: map[string]bool{
+ "srcs": true,
+ },
+ ResolveAttrs: map[string]bool{
+ "deps": true,
+ },
+ },
+}
+
+// Loads returns .bzl files and symbols they define. Every rule generated by
+// GenerateRules, now or in the past, should be loadable from one of these
+// files.
+func (py *Python) Loads() []rule.LoadInfo {
+ return pyLoads
+}
+
+var pyLoads = []rule.LoadInfo{
+ {
+ Name: "@rules_python//python:defs.bzl",
+ Symbols: []string{
+ pyBinaryKind,
+ pyLibraryKind,
+ pyTestKind,
+ },
+ },
+}
diff --git a/gazelle/python/language.go b/gazelle/python/language.go
new file mode 100644
index 0000000..568ac92
--- /dev/null
+++ b/gazelle/python/language.go
@@ -0,0 +1,33 @@
+// 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.
+
+package python
+
+import (
+ "github.com/bazelbuild/bazel-gazelle/language"
+)
+
+// Python satisfies the language.Language interface. It is the Gazelle extension
+// for Python rules.
+type Python struct {
+ Configurer
+ Resolver
+ LifeCycleManager
+}
+
+// NewLanguage initializes a new Python that satisfies the language.Language
+// interface. This is the entrypoint for the extension initialization.
+func NewLanguage() language.Language {
+ return &Python{}
+}
diff --git a/gazelle/python/lifecycle.go b/gazelle/python/lifecycle.go
new file mode 100644
index 0000000..592b322
--- /dev/null
+++ b/gazelle/python/lifecycle.go
@@ -0,0 +1,37 @@
+// 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.
+
+package python
+
+import (
+ "context"
+ "github.com/bazelbuild/bazel-gazelle/language"
+)
+
+type LifeCycleManager struct {
+ language.BaseLifecycleManager
+}
+
+func (l *LifeCycleManager) Before(ctx context.Context) {
+ startParserProcess(ctx)
+ startStdModuleProcess(ctx)
+}
+
+func (l *LifeCycleManager) DoneGeneratingRules() {
+ shutdownParserProcess()
+}
+
+func (l *LifeCycleManager) AfterResolvingDeps(ctx context.Context) {
+ shutdownStdModuleProcess()
+}
diff --git a/gazelle/python/parse.py b/gazelle/python/parse.py
new file mode 100644
index 0000000..6c0ef69
--- /dev/null
+++ b/gazelle/python/parse.py
@@ -0,0 +1,106 @@
+# 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.
+
+# parse.py is a long-living program that communicates over STDIN and STDOUT.
+# STDIN receives parse requests, one per line. It outputs the parsed modules and
+# comments from all the files from each request.
+
+import ast
+import concurrent.futures
+import json
+import os
+import sys
+from io import BytesIO
+from tokenize import COMMENT, tokenize
+
+
+def parse_import_statements(content, filepath):
+ modules = list()
+ tree = ast.parse(content, filename=filepath)
+ for node in ast.walk(tree):
+ if isinstance(node, ast.Import):
+ for subnode in node.names:
+ module = {
+ "name": subnode.name,
+ "lineno": node.lineno,
+ "filepath": filepath,
+ "from": "",
+ }
+ modules.append(module)
+ elif isinstance(node, ast.ImportFrom) and node.level == 0:
+ for subnode in node.names:
+ module = {
+ "name": f"{node.module}.{subnode.name}",
+ "lineno": node.lineno,
+ "filepath": filepath,
+ "from": node.module,
+ }
+ modules.append(module)
+ return modules
+
+
+def parse_comments(content):
+ comments = list()
+ g = tokenize(BytesIO(content.encode("utf-8")).readline)
+ for toknum, tokval, _, _, _ in g:
+ if toknum == COMMENT:
+ comments.append(tokval)
+ return comments
+
+
+def parse(repo_root, rel_package_path, filename):
+ rel_filepath = os.path.join(rel_package_path, filename)
+ abs_filepath = os.path.join(repo_root, rel_filepath)
+ with open(abs_filepath, "r") as file:
+ content = file.read()
+ # From simple benchmarks, 2 workers gave the best performance here.
+ with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
+ modules_future = executor.submit(
+ parse_import_statements, content, rel_filepath
+ )
+ comments_future = executor.submit(parse_comments, content)
+ modules = modules_future.result()
+ comments = comments_future.result()
+ output = {
+ "modules": modules,
+ "comments": comments,
+ }
+ return output
+
+
+def main(stdin, stdout):
+ with concurrent.futures.ProcessPoolExecutor() as executor:
+ for parse_request in stdin:
+ parse_request = json.loads(parse_request)
+ repo_root = parse_request["repo_root"]
+ rel_package_path = parse_request["rel_package_path"]
+ filenames = parse_request["filenames"]
+ outputs = list()
+ if len(filenames) == 1:
+ outputs.append(parse(repo_root, rel_package_path, filenames[0]))
+ else:
+ futures = [
+ executor.submit(parse, repo_root, rel_package_path, filename)
+ for filename in filenames
+ if filename != ""
+ ]
+ for future in concurrent.futures.as_completed(futures):
+ outputs.append(future.result())
+ print(json.dumps(outputs), end="", file=stdout, flush=True)
+ stdout.buffer.write(bytes([0]))
+ stdout.flush()
+
+
+if __name__ == "__main__":
+ exit(main(sys.stdin, sys.stdout))
diff --git a/gazelle/python/parser.go b/gazelle/python/parser.go
new file mode 100644
index 0000000..7f10a75
--- /dev/null
+++ b/gazelle/python/parser.go
@@ -0,0 +1,279 @@
+// 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.
+
+package python
+
+import (
+ "bufio"
+ "context"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "strings"
+ "sync"
+
+ "github.com/bazelbuild/rules_go/go/tools/bazel"
+ "github.com/emirpasic/gods/sets/treeset"
+ godsutils "github.com/emirpasic/gods/utils"
+)
+
+var (
+ parserStdin io.WriteCloser
+ parserStdout io.Reader
+ parserMutex sync.Mutex
+)
+
+func startParserProcess(ctx context.Context) {
+ parseScriptRunfile, err := bazel.Runfile("python/parse")
+ if err != nil {
+ log.Printf("failed to initialize parser: %v\n", err)
+ os.Exit(1)
+ }
+
+ cmd := exec.CommandContext(ctx, parseScriptRunfile)
+
+ cmd.Stderr = os.Stderr
+
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ log.Printf("failed to initialize parser: %v\n", err)
+ os.Exit(1)
+ }
+ parserStdin = stdin
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Printf("failed to initialize parser: %v\n", err)
+ os.Exit(1)
+ }
+ parserStdout = stdout
+
+ if err := cmd.Start(); err != nil {
+ log.Printf("failed to initialize parser: %v\n", err)
+ os.Exit(1)
+ }
+
+ go func() {
+ if err := cmd.Wait(); err != nil {
+ log.Printf("failed to wait for parser: %v\n", err)
+ os.Exit(1)
+ }
+ }()
+}
+
+func shutdownParserProcess() {
+ if err := parserStdin.Close(); err != nil {
+ fmt.Fprintf(os.Stderr, "error closing parser: %v", err)
+ }
+}
+
+// python3Parser implements a parser for Python files that extracts the modules
+// as seen in the import statements.
+type python3Parser struct {
+ // The value of language.GenerateArgs.Config.RepoRoot.
+ repoRoot string
+ // The value of language.GenerateArgs.Rel.
+ relPackagePath string
+ // The function that determines if a dependency is ignored from a Gazelle
+ // directive. It's the signature of pythonconfig.Config.IgnoresDependency.
+ ignoresDependency func(dep string) bool
+}
+
+// newPython3Parser constructs a new python3Parser.
+func newPython3Parser(
+ repoRoot string,
+ relPackagePath string,
+ ignoresDependency func(dep string) bool,
+) *python3Parser {
+ return &python3Parser{
+ repoRoot: repoRoot,
+ relPackagePath: relPackagePath,
+ ignoresDependency: ignoresDependency,
+ }
+}
+
+// parseSingle parses a single Python file and returns the extracted modules
+// from the import statements as well as the parsed comments.
+func (p *python3Parser) parseSingle(pyFilename string) (*treeset.Set, error) {
+ pyFilenames := treeset.NewWith(godsutils.StringComparator)
+ pyFilenames.Add(pyFilename)
+ return p.parse(pyFilenames)
+}
+
+// parse parses multiple Python files and returns the extracted modules from
+// the import statements as well as the parsed comments.
+func (p *python3Parser) parse(pyFilenames *treeset.Set) (*treeset.Set, error) {
+ parserMutex.Lock()
+ defer parserMutex.Unlock()
+
+ modules := treeset.NewWith(moduleComparator)
+
+ req := map[string]interface{}{
+ "repo_root": p.repoRoot,
+ "rel_package_path": p.relPackagePath,
+ "filenames": pyFilenames.Values(),
+ }
+ encoder := json.NewEncoder(parserStdin)
+ if err := encoder.Encode(&req); err != nil {
+ return nil, fmt.Errorf("failed to parse: %w", err)
+ }
+
+ reader := bufio.NewReader(parserStdout)
+ data, err := reader.ReadBytes(0)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse: %w", err)
+ }
+ data = data[:len(data)-1]
+ var allRes []parserResponse
+ if err := json.Unmarshal(data, &allRes); err != nil {
+ return nil, fmt.Errorf("failed to parse: %w", err)
+ }
+
+ for _, res := range allRes {
+ annotations, err := annotationsFromComments(res.Comments)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse annotations: %w", err)
+ }
+
+ for _, m := range res.Modules {
+ // Check for ignored dependencies set via an annotation to the Python
+ // module.
+ if annotations.ignores(m.Name) || annotations.ignores(m.From) {
+ continue
+ }
+
+ // Check for ignored dependencies set via a Gazelle directive in a BUILD
+ // file.
+ if p.ignoresDependency(m.Name) || p.ignoresDependency(m.From) {
+ continue
+ }
+
+ modules.Add(m)
+ }
+ }
+
+ return modules, nil
+}
+
+// parserResponse represents a response returned by the parser.py for a given
+// parsed Python module.
+type parserResponse struct {
+ // The modules depended by the parsed module.
+ Modules []module `json:"modules"`
+ // The comments contained in the parsed module. This contains the
+ // annotations as they are comments in the Python module.
+ Comments []comment `json:"comments"`
+}
+
+// module represents a fully-qualified, dot-separated, Python module as seen on
+// the import statement, alongside the line number where it happened.
+type module struct {
+ // The fully-qualified, dot-separated, Python module name as seen on import
+ // statements.
+ Name string `json:"name"`
+ // The line number where the import happened.
+ LineNumber uint32 `json:"lineno"`
+ // The path to the module file relative to the Bazel workspace root.
+ Filepath string `json:"filepath"`
+ // If this was a from import, e.g. from foo import bar, From indicates the module
+ // from which it is imported.
+ From string `json:"from"`
+}
+
+// moduleComparator compares modules by name.
+func moduleComparator(a, b interface{}) int {
+ return godsutils.StringComparator(a.(module).Name, b.(module).Name)
+}
+
+// annotationKind represents Gazelle annotation kinds.
+type annotationKind string
+
+const (
+ // The Gazelle annotation prefix.
+ annotationPrefix string = "gazelle:"
+ // The ignore annotation kind. E.g. '# gazelle:ignore <module_name>'.
+ annotationKindIgnore annotationKind = "ignore"
+)
+
+// comment represents a Python comment.
+type comment string
+
+// asAnnotation returns an annotation object if the comment has the
+// annotationPrefix.
+func (c *comment) asAnnotation() (*annotation, error) {
+ uncomment := strings.TrimLeft(string(*c), "# ")
+ if !strings.HasPrefix(uncomment, annotationPrefix) {
+ return nil, nil
+ }
+ withoutPrefix := strings.TrimPrefix(uncomment, annotationPrefix)
+ annotationParts := strings.SplitN(withoutPrefix, " ", 2)
+ if len(annotationParts) < 2 {
+ return nil, fmt.Errorf("`%s` requires a value", *c)
+ }
+ return &annotation{
+ kind: annotationKind(annotationParts[0]),
+ value: annotationParts[1],
+ }, nil
+}
+
+// annotation represents a single Gazelle annotation parsed from a Python
+// comment.
+type annotation struct {
+ kind annotationKind
+ value string
+}
+
+// annotations represent the collection of all Gazelle annotations parsed out of
+// the comments of a Python module.
+type annotations struct {
+ // The parsed modules to be ignored by Gazelle.
+ ignore map[string]struct{}
+}
+
+// annotationsFromComments returns all the annotations parsed out of the
+// comments of a Python module.
+func annotationsFromComments(comments []comment) (*annotations, error) {
+ ignore := make(map[string]struct{})
+ for _, comment := range comments {
+ annotation, err := comment.asAnnotation()
+ if err != nil {
+ return nil, err
+ }
+ if annotation != nil {
+ if annotation.kind == annotationKindIgnore {
+ modules := strings.Split(annotation.value, ",")
+ for _, m := range modules {
+ if m == "" {
+ continue
+ }
+ m = strings.TrimSpace(m)
+ ignore[m] = struct{}{}
+ }
+ }
+ }
+ }
+ return &annotations{
+ ignore: ignore,
+ }, nil
+}
+
+// ignored returns true if the given module was ignored via the ignore
+// annotation.
+func (a *annotations) ignores(module string) bool {
+ _, ignores := a.ignore[module]
+ return ignores
+}
diff --git a/gazelle/python/python_test.go b/gazelle/python/python_test.go
new file mode 100644
index 0000000..79450ad
--- /dev/null
+++ b/gazelle/python/python_test.go
@@ -0,0 +1,206 @@
+/* Copyright 2020 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.
+*/
+
+// This test file was first seen on:
+// https://github.com/bazelbuild/bazel-skylib/blob/f80bc733d4b9f83d427ce3442be2e07427b2cc8d/gazelle/bzl/BUILD.
+// It was modified for the needs of this extension.
+
+package python_test
+
+import (
+ "bytes"
+ "context"
+ "errors"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/bazelbuild/bazel-gazelle/testtools"
+ "github.com/bazelbuild/rules_go/go/tools/bazel"
+ "github.com/ghodss/yaml"
+)
+
+const (
+ extensionDir = "python" + string(os.PathSeparator)
+ testDataPath = extensionDir + "testdata" + string(os.PathSeparator)
+ gazelleBinaryName = "gazelle_binary"
+)
+
+var gazellePath = mustFindGazelle()
+
+func TestGazelleBinary(t *testing.T) {
+ tests := map[string][]bazel.RunfileEntry{}
+
+ runfiles, err := bazel.ListRunfiles()
+ if err != nil {
+ t.Fatalf("bazel.ListRunfiles() error: %v", err)
+ }
+ for _, f := range runfiles {
+ if strings.HasPrefix(f.ShortPath, testDataPath) {
+ relativePath := strings.TrimPrefix(f.ShortPath, testDataPath)
+ parts := strings.SplitN(relativePath, string(os.PathSeparator), 2)
+ if len(parts) < 2 {
+ // This file is not a part of a testcase since it must be in a dir that
+ // is the test case and then have a path inside of that.
+ continue
+ }
+
+ tests[parts[0]] = append(tests[parts[0]], f)
+ }
+ }
+ if len(tests) == 0 {
+ t.Fatal("no tests found")
+ }
+
+ for testName, files := range tests {
+ testPath(t, testName, files)
+ }
+}
+
+func testPath(t *testing.T, name string, files []bazel.RunfileEntry) {
+ t.Run(name, func(t *testing.T) {
+ t.Parallel()
+ var inputs, goldens []testtools.FileSpec
+
+ var config *testYAML
+ for _, f := range files {
+ path := f.Path
+ trim := filepath.Join(testDataPath, name) + string(os.PathSeparator)
+ shortPath := strings.TrimPrefix(f.ShortPath, trim)
+ info, err := os.Stat(path)
+ if err != nil {
+ t.Fatalf("os.Stat(%q) error: %v", path, err)
+ }
+
+ if info.IsDir() {
+ continue
+ }
+
+ content, err := os.ReadFile(path)
+ if err != nil {
+ t.Errorf("os.ReadFile(%q) error: %v", path, err)
+ }
+
+ if filepath.Base(shortPath) == "test.yaml" {
+ if config != nil {
+ t.Fatal("only 1 test.yaml is supported")
+ }
+ config = new(testYAML)
+ if err := yaml.Unmarshal(content, config); err != nil {
+ t.Fatal(err)
+ }
+ }
+
+ if strings.HasSuffix(shortPath, ".in") {
+ inputs = append(inputs, testtools.FileSpec{
+ Path: filepath.Join(name, strings.TrimSuffix(shortPath, ".in")),
+ Content: string(content),
+ })
+ continue
+ }
+
+ if strings.HasSuffix(shortPath, ".out") {
+ goldens = append(goldens, testtools.FileSpec{
+ Path: filepath.Join(name, strings.TrimSuffix(shortPath, ".out")),
+ Content: string(content),
+ })
+ continue
+ }
+
+ inputs = append(inputs, testtools.FileSpec{
+ Path: filepath.Join(name, shortPath),
+ Content: string(content),
+ })
+ goldens = append(goldens, testtools.FileSpec{
+ Path: filepath.Join(name, shortPath),
+ Content: string(content),
+ })
+ }
+
+ testdataDir, cleanup := testtools.CreateFiles(t, inputs)
+ t.Cleanup(cleanup)
+ t.Cleanup(func() {
+ if !t.Failed() {
+ return
+ }
+
+ filepath.Walk(testdataDir, func(path string, info os.FileInfo, err error) error {
+ if err != nil {
+ return err
+ }
+ t.Logf("%q exists", strings.TrimPrefix(path, testdataDir))
+ return nil
+ })
+ })
+
+ workspaceRoot := filepath.Join(testdataDir, name)
+
+ args := []string{"-build_file_name=BUILD,BUILD.bazel"}
+
+ ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
+ t.Cleanup(cancel)
+ cmd := exec.CommandContext(ctx, gazellePath, args...)
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+ cmd.Dir = workspaceRoot
+ if err := cmd.Run(); err != nil {
+ var e *exec.ExitError
+ if !errors.As(err, &e) {
+ t.Fatal(err)
+ }
+ }
+
+ actualExitCode := cmd.ProcessState.ExitCode()
+ if config.Expect.ExitCode != actualExitCode {
+ t.Errorf("expected gazelle exit code: %d\ngot: %d",
+ config.Expect.ExitCode, actualExitCode)
+ }
+ actualStdout := stdout.String()
+ if strings.TrimSpace(config.Expect.Stdout) != strings.TrimSpace(actualStdout) {
+ t.Errorf("expected gazelle stdout: %s\ngot: %s",
+ config.Expect.Stdout, actualStdout)
+ }
+ actualStderr := stderr.String()
+ if strings.TrimSpace(config.Expect.Stderr) != strings.TrimSpace(actualStderr) {
+ t.Errorf("expected gazelle stderr: %s\ngot: %s",
+ config.Expect.Stderr, actualStderr)
+ }
+ if t.Failed() {
+ t.FailNow()
+ }
+
+ testtools.CheckFiles(t, testdataDir, goldens)
+ })
+}
+
+func mustFindGazelle() string {
+ gazellePath, ok := bazel.FindBinary(extensionDir, gazelleBinaryName)
+ if !ok {
+ panic("could not find gazelle binary")
+ }
+ return gazellePath
+}
+
+type testYAML struct {
+ Expect struct {
+ ExitCode int `json:"exit_code"`
+ Stdout string `json:"stdout"`
+ Stderr string `json:"stderr"`
+ } `json:"expect"`
+}
diff --git a/gazelle/python/resolve.go b/gazelle/python/resolve.go
new file mode 100644
index 0000000..46014e5
--- /dev/null
+++ b/gazelle/python/resolve.go
@@ -0,0 +1,304 @@
+// 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.
+
+package python
+
+import (
+ "fmt"
+ "log"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/bazelbuild/bazel-gazelle/config"
+ "github.com/bazelbuild/bazel-gazelle/label"
+ "github.com/bazelbuild/bazel-gazelle/repo"
+ "github.com/bazelbuild/bazel-gazelle/resolve"
+ "github.com/bazelbuild/bazel-gazelle/rule"
+ bzl "github.com/bazelbuild/buildtools/build"
+ "github.com/emirpasic/gods/sets/treeset"
+ godsutils "github.com/emirpasic/gods/utils"
+
+ "github.com/bazelbuild/rules_python/gazelle/pythonconfig"
+)
+
+const languageName = "py"
+
+const (
+ // resolvedDepsKey is the attribute key used to pass dependencies that don't
+ // need to be resolved by the dependency resolver in the Resolver step.
+ resolvedDepsKey = "_gazelle_python_resolved_deps"
+)
+
+// Resolver satisfies the resolve.Resolver interface. It resolves dependencies
+// in rules generated by this extension.
+type Resolver struct{}
+
+// Name returns the name of the language. This is the prefix of the kinds of
+// rules generated. E.g. py_library and py_binary.
+func (*Resolver) Name() string { return languageName }
+
+// Imports returns a list of ImportSpecs that can be used to import the rule
+// r. This is used to populate RuleIndex.
+//
+// If nil is returned, the rule will not be indexed. If any non-nil slice is
+// returned, including an empty slice, the rule will be indexed.
+func (py *Resolver) Imports(c *config.Config, r *rule.Rule, f *rule.File) []resolve.ImportSpec {
+ cfgs := c.Exts[languageName].(pythonconfig.Configs)
+ cfg := cfgs[f.Pkg]
+ srcs := r.AttrStrings("srcs")
+ provides := make([]resolve.ImportSpec, 0, len(srcs)+1)
+ for _, src := range srcs {
+ ext := filepath.Ext(src)
+ if ext == ".py" {
+ pythonProjectRoot := cfg.PythonProjectRoot()
+ provide := importSpecFromSrc(pythonProjectRoot, f.Pkg, src)
+ provides = append(provides, provide)
+ }
+ }
+ if len(provides) == 0 {
+ return nil
+ }
+ return provides
+}
+
+// importSpecFromSrc determines the ImportSpec based on the target that contains the src so that
+// the target can be indexed for import statements that match the calculated src relative to the its
+// Python project root.
+func importSpecFromSrc(pythonProjectRoot, bzlPkg, src string) resolve.ImportSpec {
+ pythonPkgDir := filepath.Join(bzlPkg, filepath.Dir(src))
+ relPythonPkgDir, err := filepath.Rel(pythonProjectRoot, pythonPkgDir)
+ if err != nil {
+ panic(fmt.Errorf("unexpected failure: %v", err))
+ }
+ if relPythonPkgDir == "." {
+ relPythonPkgDir = ""
+ }
+ pythonPkg := strings.ReplaceAll(relPythonPkgDir, "/", ".")
+ filename := filepath.Base(src)
+ if filename == pyLibraryEntrypointFilename {
+ if pythonPkg != "" {
+ return resolve.ImportSpec{
+ Lang: languageName,
+ Imp: pythonPkg,
+ }
+ }
+ }
+ moduleName := strings.TrimSuffix(filename, ".py")
+ var imp string
+ if pythonPkg == "" {
+ imp = moduleName
+ } else {
+ imp = fmt.Sprintf("%s.%s", pythonPkg, moduleName)
+ }
+ return resolve.ImportSpec{
+ Lang: languageName,
+ Imp: imp,
+ }
+}
+
+// Embeds returns a list of labels of rules that the given rule embeds. If
+// a rule is embedded by another importable rule of the same language, only
+// the embedding rule will be indexed. The embedding rule will inherit
+// the imports of the embedded rule.
+func (py *Resolver) Embeds(r *rule.Rule, from label.Label) []label.Label {
+ // TODO(f0rmiga): implement.
+ return make([]label.Label, 0)
+}
+
+// Resolve translates imported libraries for a given rule into Bazel
+// dependencies. Information about imported libraries is returned for each
+// rule generated by language.GenerateRules in
+// language.GenerateResult.Imports. Resolve generates a "deps" attribute (or
+// the appropriate language-specific equivalent) for each import according to
+// language-specific rules and heuristics.
+func (py *Resolver) Resolve(
+ c *config.Config,
+ ix *resolve.RuleIndex,
+ rc *repo.RemoteCache,
+ r *rule.Rule,
+ modulesRaw interface{},
+ from label.Label,
+) {
+ // TODO(f0rmiga): may need to be defensive here once this Gazelle extension
+ // join with the main Gazelle binary with other rules. It may conflict with
+ // other generators that generate py_* targets.
+ deps := treeset.NewWith(godsutils.StringComparator)
+ if modulesRaw != nil {
+ cfgs := c.Exts[languageName].(pythonconfig.Configs)
+ cfg := cfgs[from.Pkg]
+ pythonProjectRoot := cfg.PythonProjectRoot()
+ modules := modulesRaw.(*treeset.Set)
+ it := modules.Iterator()
+ explainDependency := os.Getenv("EXPLAIN_DEPENDENCY")
+ hasFatalError := false
+ MODULES_LOOP:
+ for it.Next() {
+ mod := it.Value().(module)
+ moduleParts := strings.Split(mod.Name, ".")
+ possibleModules := []string{mod.Name}
+ for len(moduleParts) > 1 {
+ // Iterate back through the possible imports until
+ // a match is found.
+ // For example, "from foo.bar import baz" where bar is a variable, we should try
+ // `foo.bar.baz` first, then `foo.bar`, then `foo`. In the first case, the import could be file `baz.py`
+ // in the directory `foo/bar`.
+ // Or, the import could be variable `bar` in file `foo/bar.py`.
+ // The import could also be from a standard module, e.g. `six.moves`, where
+ // the dependency is actually `six`.
+ moduleParts = moduleParts[:len(moduleParts)-1]
+ possibleModules = append(possibleModules, strings.Join(moduleParts, "."))
+ }
+ errs := []error{}
+ POSSIBLE_MODULE_LOOP:
+ for _, moduleName := range possibleModules {
+ imp := resolve.ImportSpec{Lang: languageName, Imp: moduleName}
+ if override, ok := resolve.FindRuleWithOverride(c, imp, languageName); ok {
+ if override.Repo == "" {
+ override.Repo = from.Repo
+ }
+ if !override.Equal(from) {
+ if override.Repo == from.Repo {
+ override.Repo = ""
+ }
+ dep := override.String()
+ deps.Add(dep)
+ if explainDependency == dep {
+ log.Printf("Explaining dependency (%s): "+
+ "in the target %q, the file %q imports %q at line %d, "+
+ "which resolves using the \"gazelle:resolve\" directive.\n",
+ explainDependency, from.String(), mod.Filepath, moduleName, mod.LineNumber)
+ }
+ continue MODULES_LOOP
+ }
+ } else {
+ if dep, ok := cfg.FindThirdPartyDependency(moduleName); ok {
+ deps.Add(dep)
+ if explainDependency == dep {
+ log.Printf("Explaining dependency (%s): "+
+ "in the target %q, the file %q imports %q at line %d, "+
+ "which resolves from the third-party module %q from the wheel %q.\n",
+ explainDependency, from.String(), mod.Filepath, moduleName, mod.LineNumber, mod.Name, dep)
+ }
+ continue MODULES_LOOP
+ } else {
+ matches := ix.FindRulesByImportWithConfig(c, imp, languageName)
+ if len(matches) == 0 {
+ // Check if the imported module is part of the standard library.
+ if isStd, err := isStdModule(module{Name: moduleName}); err != nil {
+ log.Println("Error checking if standard module: ", err)
+ hasFatalError = true
+ continue POSSIBLE_MODULE_LOOP
+ } else if isStd {
+ continue MODULES_LOOP
+ } else if cfg.ValidateImportStatements() {
+ err := fmt.Errorf(
+ "%[1]q at line %[2]d from %[3]q is an invalid dependency: possible solutions:\n"+
+ "\t1. Add it as a dependency in the requirements.txt file.\n"+
+ "\t2. Instruct Gazelle to resolve to a known dependency using the gazelle:resolve directive.\n"+
+ "\t3. Ignore it with a comment '# gazelle:ignore %[1]s' in the Python file.\n",
+ moduleName, mod.LineNumber, mod.Filepath,
+ )
+ errs = append(errs, err)
+ continue POSSIBLE_MODULE_LOOP
+ }
+ }
+ filteredMatches := make([]resolve.FindResult, 0, len(matches))
+ for _, match := range matches {
+ if match.IsSelfImport(from) {
+ // Prevent from adding itself as a dependency.
+ continue MODULES_LOOP
+ }
+ filteredMatches = append(filteredMatches, match)
+ }
+ if len(filteredMatches) == 0 {
+ continue POSSIBLE_MODULE_LOOP
+ }
+ if len(filteredMatches) > 1 {
+ sameRootMatches := make([]resolve.FindResult, 0, len(filteredMatches))
+ for _, match := range filteredMatches {
+ if strings.HasPrefix(match.Label.Pkg, pythonProjectRoot) {
+ sameRootMatches = append(sameRootMatches, match)
+ }
+ }
+ if len(sameRootMatches) != 1 {
+ err := fmt.Errorf(
+ "multiple targets (%s) may be imported with %q at line %d in %q "+
+ "- this must be fixed using the \"gazelle:resolve\" directive",
+ targetListFromResults(filteredMatches), moduleName, mod.LineNumber, mod.Filepath)
+ errs = append(errs, err)
+ continue POSSIBLE_MODULE_LOOP
+ }
+ filteredMatches = sameRootMatches
+ }
+ matchLabel := filteredMatches[0].Label.Rel(from.Repo, from.Pkg)
+ dep := matchLabel.String()
+ deps.Add(dep)
+ if explainDependency == dep {
+ log.Printf("Explaining dependency (%s): "+
+ "in the target %q, the file %q imports %q at line %d, "+
+ "which resolves from the first-party indexed labels.\n",
+ explainDependency, from.String(), mod.Filepath, moduleName, mod.LineNumber)
+ }
+ continue MODULES_LOOP
+ }
+ }
+ } // End possible modules loop.
+ if len(errs) > 0 {
+ // If, after trying all possible modules, we still haven't found anything, error out.
+ joinedErrs := ""
+ for _, err := range errs {
+ joinedErrs = fmt.Sprintf("%s%s\n", joinedErrs, err)
+ }
+ log.Printf("ERROR: failed to validate dependencies for target %q: %v\n", from.String(), joinedErrs)
+ hasFatalError = true
+ }
+ }
+ if hasFatalError {
+ os.Exit(1)
+ }
+ }
+ resolvedDeps := r.PrivateAttr(resolvedDepsKey).(*treeset.Set)
+ if !resolvedDeps.Empty() {
+ it := resolvedDeps.Iterator()
+ for it.Next() {
+ deps.Add(it.Value())
+ }
+ }
+ if !deps.Empty() {
+ r.SetAttr("deps", convertDependencySetToExpr(deps))
+ }
+}
+
+// targetListFromResults returns a string with the human-readable list of
+// targets contained in the given results.
+func targetListFromResults(results []resolve.FindResult) string {
+ list := make([]string, len(results))
+ for i, result := range results {
+ list[i] = result.Label.String()
+ }
+ return strings.Join(list, ", ")
+}
+
+// convertDependencySetToExpr converts the given set of dependencies to an
+// expression to be used in the deps attribute.
+func convertDependencySetToExpr(set *treeset.Set) bzl.Expr {
+ deps := make([]bzl.Expr, set.Size())
+ it := set.Iterator()
+ for it.Next() {
+ dep := it.Value().(string)
+ deps[it.Index()] = &bzl.StringExpr{Value: dep}
+ }
+ return &bzl.ListExpr{List: deps}
+}
diff --git a/gazelle/python/std_modules.go b/gazelle/python/std_modules.go
new file mode 100644
index 0000000..c537184
--- /dev/null
+++ b/gazelle/python/std_modules.go
@@ -0,0 +1,114 @@
+// 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.
+
+package python
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "io"
+ "log"
+ "os"
+ "os/exec"
+ "strconv"
+ "strings"
+ "sync"
+
+ "github.com/bazelbuild/rules_go/go/tools/bazel"
+)
+
+var (
+ stdModulesStdin io.WriteCloser
+ stdModulesStdout io.Reader
+ stdModulesMutex sync.Mutex
+ stdModulesSeen map[string]struct{}
+)
+
+func startStdModuleProcess(ctx context.Context) {
+ stdModulesSeen = make(map[string]struct{})
+
+ stdModulesScriptRunfile, err := bazel.Runfile("python/std_modules")
+ if err != nil {
+ log.Printf("failed to initialize std_modules: %v\n", err)
+ os.Exit(1)
+ }
+
+ cmd := exec.CommandContext(ctx, stdModulesScriptRunfile)
+
+ cmd.Stderr = os.Stderr
+ // All userland site-packages should be ignored.
+ cmd.Env = []string{"PYTHONNOUSERSITE=1"}
+ stdin, err := cmd.StdinPipe()
+ if err != nil {
+ log.Printf("failed to initialize std_modules: %v\n", err)
+ os.Exit(1)
+ }
+ stdModulesStdin = stdin
+
+ stdout, err := cmd.StdoutPipe()
+ if err != nil {
+ log.Printf("failed to initialize std_modules: %v\n", err)
+ os.Exit(1)
+ }
+ stdModulesStdout = stdout
+
+ if err := cmd.Start(); err != nil {
+ log.Printf("failed to initialize std_modules: %v\n", err)
+ os.Exit(1)
+ }
+
+ go func() {
+ if err := cmd.Wait(); err != nil {
+ log.Printf("failed to wait for std_modules: %v\n", err)
+ os.Exit(1)
+ }
+ }()
+}
+
+func shutdownStdModuleProcess() {
+ if err := stdModulesStdin.Close(); err != nil {
+ fmt.Fprintf(os.Stderr, "error closing std module: %v", err)
+ }
+}
+
+func isStdModule(m module) (bool, error) {
+ if _, seen := stdModulesSeen[m.Name]; seen {
+ return true, nil
+ }
+ stdModulesMutex.Lock()
+ defer stdModulesMutex.Unlock()
+
+ fmt.Fprintf(stdModulesStdin, "%s\n", m.Name)
+
+ stdoutReader := bufio.NewReader(stdModulesStdout)
+ line, err := stdoutReader.ReadString('\n')
+ if err != nil {
+ return false, err
+ }
+ if len(line) == 0 {
+ return false, fmt.Errorf("unexpected empty output from std_modules")
+ }
+
+ isStd, err := strconv.ParseBool(strings.TrimSpace(line))
+ if err != nil {
+ return false, err
+ }
+
+ if isStd {
+ stdModulesSeen[m.Name] = struct{}{}
+ return true, nil
+ }
+ return false, nil
+}
diff --git a/gazelle/python/std_modules.py b/gazelle/python/std_modules.py
new file mode 100644
index 0000000..779a325
--- /dev/null
+++ b/gazelle/python/std_modules.py
@@ -0,0 +1,51 @@
+# 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.
+
+# std_modules.py is a long-living program that communicates over STDIN and
+# STDOUT. STDIN receives module names, one per line. For each module statement
+# it evaluates, it outputs true/false for whether the module is part of the
+# standard library or not.
+
+import os
+import sys
+from contextlib import redirect_stdout
+
+
+def is_std_modules(module):
+ # If for some reason a module (such as pygame, see https://github.com/pygame/pygame/issues/542)
+ # prints to stdout upon import,
+ # the output of this script should still be parseable by golang.
+ # Therefore, redirect stdout while running the import.
+ with redirect_stdout(os.devnull):
+ try:
+ __import__(module, globals(), locals(), [], 0)
+ return True
+ except Exception:
+ return False
+
+
+def main(stdin, stdout):
+ for module in stdin:
+ module = module.strip()
+ # Don't print the boolean directly as it is capitalized in Python.
+ print(
+ "true" if is_std_modules(module) else "false",
+ end="\n",
+ file=stdout,
+ )
+ stdout.flush()
+
+
+if __name__ == "__main__":
+ exit(main(sys.stdin, sys.stdout))
diff --git a/gazelle/python/target.go b/gazelle/python/target.go
new file mode 100644
index 0000000..fdc99fc
--- /dev/null
+++ b/gazelle/python/target.go
@@ -0,0 +1,157 @@
+// 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.
+
+package python
+
+import (
+ "github.com/bazelbuild/bazel-gazelle/config"
+ "github.com/bazelbuild/bazel-gazelle/rule"
+ "github.com/emirpasic/gods/sets/treeset"
+ godsutils "github.com/emirpasic/gods/utils"
+ "path/filepath"
+)
+
+// targetBuilder builds targets to be generated by Gazelle.
+type targetBuilder struct {
+ kind string
+ name string
+ pythonProjectRoot string
+ bzlPackage string
+ srcs *treeset.Set
+ siblingSrcs *treeset.Set
+ deps *treeset.Set
+ resolvedDeps *treeset.Set
+ visibility *treeset.Set
+ main *string
+ imports []string
+ testonly bool
+}
+
+// newTargetBuilder constructs a new targetBuilder.
+func newTargetBuilder(kind, name, pythonProjectRoot, bzlPackage string, siblingSrcs *treeset.Set) *targetBuilder {
+ return &targetBuilder{
+ kind: kind,
+ name: name,
+ pythonProjectRoot: pythonProjectRoot,
+ bzlPackage: bzlPackage,
+ srcs: treeset.NewWith(godsutils.StringComparator),
+ siblingSrcs: siblingSrcs,
+ deps: treeset.NewWith(moduleComparator),
+ resolvedDeps: treeset.NewWith(godsutils.StringComparator),
+ visibility: treeset.NewWith(godsutils.StringComparator),
+ }
+}
+
+// addSrc adds a single src to the target.
+func (t *targetBuilder) addSrc(src string) *targetBuilder {
+ t.srcs.Add(src)
+ return t
+}
+
+// addSrcs copies all values from the provided srcs to the target.
+func (t *targetBuilder) addSrcs(srcs *treeset.Set) *targetBuilder {
+ it := srcs.Iterator()
+ for it.Next() {
+ t.srcs.Add(it.Value().(string))
+ }
+ return t
+}
+
+// addModuleDependency adds a single module dep to the target.
+func (t *targetBuilder) addModuleDependency(dep module) *targetBuilder {
+ fileName := dep.Name + ".py"
+ if dep.From != "" {
+ fileName = dep.From + ".py"
+ }
+ if t.siblingSrcs.Contains(fileName) && fileName != filepath.Base(dep.Filepath) {
+ // importing another module from the same package, converting to absolute imports to make
+ // dependency resolution easier
+ dep.Name = importSpecFromSrc(t.pythonProjectRoot, t.bzlPackage, fileName).Imp
+ }
+ t.deps.Add(dep)
+ return t
+}
+
+// addModuleDependencies copies all values from the provided deps to the target.
+func (t *targetBuilder) addModuleDependencies(deps *treeset.Set) *targetBuilder {
+ it := deps.Iterator()
+ for it.Next() {
+ t.addModuleDependency(it.Value().(module))
+ }
+ return t
+}
+
+// addResolvedDependency adds a single dependency the target that has already
+// been resolved or generated. The Resolver step doesn't process it further.
+func (t *targetBuilder) addResolvedDependency(dep string) *targetBuilder {
+ t.resolvedDeps.Add(dep)
+ return t
+}
+
+// addVisibility adds a visibility to the target.
+func (t *targetBuilder) addVisibility(visibility string) *targetBuilder {
+ t.visibility.Add(visibility)
+ return t
+}
+
+// setMain sets the main file to the target.
+func (t *targetBuilder) setMain(main string) *targetBuilder {
+ t.main = &main
+ return t
+}
+
+// setTestonly sets the testonly attribute to true.
+func (t *targetBuilder) setTestonly() *targetBuilder {
+ t.testonly = true
+ return t
+}
+
+// generateImportsAttribute generates the imports attribute.
+// These are a list of import directories to be added to the PYTHONPATH. In our
+// case, the value we add is on Bazel sub-packages to be able to perform imports
+// relative to the root project package.
+func (t *targetBuilder) generateImportsAttribute() *targetBuilder {
+ p, _ := filepath.Rel(t.bzlPackage, t.pythonProjectRoot)
+ p = filepath.Clean(p)
+ if p == "." {
+ return t
+ }
+ t.imports = []string{p}
+ return t
+}
+
+// build returns the assembled *rule.Rule for the target.
+func (t *targetBuilder) build() *rule.Rule {
+ r := rule.NewRule(t.kind, t.name)
+ if !t.srcs.Empty() {
+ r.SetAttr("srcs", t.srcs.Values())
+ }
+ if !t.visibility.Empty() {
+ r.SetAttr("visibility", t.visibility.Values())
+ }
+ if t.main != nil {
+ r.SetAttr("main", *t.main)
+ }
+ if t.imports != nil {
+ r.SetAttr("imports", t.imports)
+ }
+ if !t.deps.Empty() {
+ r.SetPrivateAttr(config.GazelleImportsKey, t.deps)
+ }
+ if t.testonly {
+ r.SetAttr("testonly", true)
+ }
+ r.SetPrivateAttr(resolvedDepsKey, t.resolvedDeps)
+ return r
+}
diff --git a/gazelle/python/testdata/README.md b/gazelle/python/testdata/README.md
new file mode 100644
index 0000000..6c25d48
--- /dev/null
+++ b/gazelle/python/testdata/README.md
@@ -0,0 +1,12 @@
+# Gazelle Python extension test cases
+
+Each directory is a test case that contains `BUILD.in` and `BUILD.out` files for
+assertion. `BUILD.in` is used as how the build file looks before running
+Gazelle, and `BUILD.out` how the build file should look like after running
+Gazelle.
+
+Each test case is a Bazel workspace and Gazelle will run with its working
+directory set to the root of this workspace, though, the test runner will find
+`test.yaml` files and use them to determine the directory Gazelle should use for
+each inner Python project. The `test.yaml` file is a manifest for the test -
+check for the existing ones for examples.
diff --git a/gazelle/python/testdata/dependency_resolution_order/BUILD.in b/gazelle/python/testdata/dependency_resolution_order/BUILD.in
new file mode 100644
index 0000000..71a5c5a
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/BUILD.in
@@ -0,0 +1 @@
+# gazelle:resolve py bar //somewhere/bar
diff --git a/gazelle/python/testdata/dependency_resolution_order/BUILD.out b/gazelle/python/testdata/dependency_resolution_order/BUILD.out
new file mode 100644
index 0000000..3ea83eb
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/BUILD.out
@@ -0,0 +1,14 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:resolve py bar //somewhere/bar
+
+py_library(
+ name = "dependency_resolution_order",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//baz",
+ "//somewhere/bar",
+ "@gazelle_python_test_some_foo//:pkg",
+ ],
+)
diff --git a/gazelle/python/testdata/dependency_resolution_order/README.md b/gazelle/python/testdata/dependency_resolution_order/README.md
new file mode 100644
index 0000000..75ceb0b
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/README.md
@@ -0,0 +1,7 @@
+# Dependency resolution order
+
+This asserts that the generator resolves the dependencies in the right order:
+
+1. Explicit resolution via gazelle:resolve.
+2. Third-party dependencies matching in the `modules_mapping.json`.
+3. Indexed generated first-party dependencies.
diff --git a/gazelle/python/testdata/dependency_resolution_order/WORKSPACE b/gazelle/python/testdata/dependency_resolution_order/WORKSPACE
new file mode 100644
index 0000000..4959898
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/WORKSPACE
@@ -0,0 +1 @@
+# This is a test data Bazel workspace.
diff --git a/gazelle/python/testdata/dependency_resolution_order/__init__.py b/gazelle/python/testdata/dependency_resolution_order/__init__.py
new file mode 100644
index 0000000..d9c6504
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/__init__.py
@@ -0,0 +1,24 @@
+# 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.
+
+import sys
+
+import bar
+import baz
+import foo
+
+_ = sys
+_ = bar
+_ = baz
+_ = foo
diff --git a/gazelle/python/testdata/dependency_resolution_order/bar/BUILD.in b/gazelle/python/testdata/dependency_resolution_order/bar/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/bar/BUILD.in
diff --git a/gazelle/python/testdata/dependency_resolution_order/bar/BUILD.out b/gazelle/python/testdata/dependency_resolution_order/bar/BUILD.out
new file mode 100644
index 0000000..da9915d
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/bar/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "bar",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/dependency_resolution_order/bar/__init__.py b/gazelle/python/testdata/dependency_resolution_order/bar/__init__.py
new file mode 100644
index 0000000..1c0275c
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/bar/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+import os
+
+_ = os
diff --git a/gazelle/python/testdata/dependency_resolution_order/baz/BUILD.in b/gazelle/python/testdata/dependency_resolution_order/baz/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/baz/BUILD.in
diff --git a/gazelle/python/testdata/dependency_resolution_order/baz/BUILD.out b/gazelle/python/testdata/dependency_resolution_order/baz/BUILD.out
new file mode 100644
index 0000000..749fd3d
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/baz/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "baz",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/dependency_resolution_order/baz/__init__.py b/gazelle/python/testdata/dependency_resolution_order/baz/__init__.py
new file mode 100644
index 0000000..1c0275c
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/baz/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+import os
+
+_ = os
diff --git a/gazelle/python/testdata/dependency_resolution_order/foo/BUILD.in b/gazelle/python/testdata/dependency_resolution_order/foo/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/foo/BUILD.in
diff --git a/gazelle/python/testdata/dependency_resolution_order/foo/BUILD.out b/gazelle/python/testdata/dependency_resolution_order/foo/BUILD.out
new file mode 100644
index 0000000..4404d30
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/foo/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/dependency_resolution_order/foo/__init__.py b/gazelle/python/testdata/dependency_resolution_order/foo/__init__.py
new file mode 100644
index 0000000..1c0275c
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/foo/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+import os
+
+_ = os
diff --git a/gazelle/python/testdata/dependency_resolution_order/gazelle_python.yaml b/gazelle/python/testdata/dependency_resolution_order/gazelle_python.yaml
new file mode 100644
index 0000000..8615181
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ foo: some_foo
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.in b/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.in
diff --git a/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.out b/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.out
new file mode 100644
index 0000000..a0d421b
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "bar",
+ srcs = ["__init__.py"],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/__init__.py b/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/__init__.py
new file mode 100644
index 0000000..1c0275c
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/somewhere/bar/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+import os
+
+_ = os
diff --git a/gazelle/python/testdata/dependency_resolution_order/test.yaml b/gazelle/python/testdata/dependency_resolution_order/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/dependency_resolution_order/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/disable_import_statements_validation/BUILD.in b/gazelle/python/testdata/disable_import_statements_validation/BUILD.in
new file mode 100644
index 0000000..741aff6
--- /dev/null
+++ b/gazelle/python/testdata/disable_import_statements_validation/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_validate_import_statements false
diff --git a/gazelle/python/testdata/disable_import_statements_validation/BUILD.out b/gazelle/python/testdata/disable_import_statements_validation/BUILD.out
new file mode 100644
index 0000000..964db6d
--- /dev/null
+++ b/gazelle/python/testdata/disable_import_statements_validation/BUILD.out
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_validate_import_statements false
+
+py_library(
+ name = "disable_import_statements_validation",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/disable_import_statements_validation/README.md b/gazelle/python/testdata/disable_import_statements_validation/README.md
new file mode 100644
index 0000000..a80fffe
--- /dev/null
+++ b/gazelle/python/testdata/disable_import_statements_validation/README.md
@@ -0,0 +1,3 @@
+# Disable import statements validation
+
+This test case asserts that the module's validation step is not performed.
diff --git a/gazelle/python/testdata/disable_import_statements_validation/WORKSPACE b/gazelle/python/testdata/disable_import_statements_validation/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/disable_import_statements_validation/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/disable_import_statements_validation/__init__.py b/gazelle/python/testdata/disable_import_statements_validation/__init__.py
new file mode 100644
index 0000000..fde6e50
--- /dev/null
+++ b/gazelle/python/testdata/disable_import_statements_validation/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+import abcdefg
+
+_ = abcdefg
diff --git a/gazelle/python/testdata/disable_import_statements_validation/test.yaml b/gazelle/python/testdata/disable_import_statements_validation/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/disable_import_statements_validation/test.yaml
@@ -0,0 +1,17 @@
+# 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.
+
+---
+expect:
+ exit_code: 0
diff --git a/gazelle/python/testdata/dont_rename_target/BUILD.in b/gazelle/python/testdata/dont_rename_target/BUILD.in
new file mode 100644
index 0000000..33e8ec2
--- /dev/null
+++ b/gazelle/python/testdata/dont_rename_target/BUILD.in
@@ -0,0 +1,5 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "my_custom_target",
+)
diff --git a/gazelle/python/testdata/dont_rename_target/BUILD.out b/gazelle/python/testdata/dont_rename_target/BUILD.out
new file mode 100644
index 0000000..62772e3
--- /dev/null
+++ b/gazelle/python/testdata/dont_rename_target/BUILD.out
@@ -0,0 +1,7 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "my_custom_target",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/dont_rename_target/README.md b/gazelle/python/testdata/dont_rename_target/README.md
new file mode 100644
index 0000000..19f9d66
--- /dev/null
+++ b/gazelle/python/testdata/dont_rename_target/README.md
@@ -0,0 +1,4 @@
+# Don't rename target
+
+This test case asserts that an existing target with a custom name doesn't get
+renamed by the Gazelle extension.
diff --git a/gazelle/python/testdata/dont_rename_target/WORKSPACE b/gazelle/python/testdata/dont_rename_target/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/dont_rename_target/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/dont_rename_target/__init__.py b/gazelle/python/testdata/dont_rename_target/__init__.py
new file mode 100644
index 0000000..bbdfb4c
--- /dev/null
+++ b/gazelle/python/testdata/dont_rename_target/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/gazelle/python/testdata/dont_rename_target/test.yaml b/gazelle/python/testdata/dont_rename_target/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/dont_rename_target/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/BUILD.in b/gazelle/python/testdata/file_name_matches_import_statement/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/BUILD.in
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/BUILD.out b/gazelle/python/testdata/file_name_matches_import_statement/BUILD.out
new file mode 100644
index 0000000..0216e4b
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "file_name_matches_import_statement",
+ srcs = [
+ "__init__.py",
+ "rest_framework.py",
+ ],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test_djangorestframework//:pkg"],
+)
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/README.md b/gazelle/python/testdata/file_name_matches_import_statement/README.md
new file mode 100644
index 0000000..591adc1
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/README.md
@@ -0,0 +1,4 @@
+# File name matches import statement
+
+This test case asserts that a file with an import statement that matches its own
+name does the right thing of resolving the third-party package.
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/WORKSPACE b/gazelle/python/testdata/file_name_matches_import_statement/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/__init__.py b/gazelle/python/testdata/file_name_matches_import_statement/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/gazelle_python.yaml b/gazelle/python/testdata/file_name_matches_import_statement/gazelle_python.yaml
new file mode 100644
index 0000000..f50d3ae
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ rest_framework: djangorestframework
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/rest_framework.py b/gazelle/python/testdata/file_name_matches_import_statement/rest_framework.py
new file mode 100644
index 0000000..43098d2
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/rest_framework.py
@@ -0,0 +1,17 @@
+# 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.
+
+import rest_framework
+
+_ = rest_framework
diff --git a/gazelle/python/testdata/file_name_matches_import_statement/test.yaml b/gazelle/python/testdata/file_name_matches_import_statement/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/file_name_matches_import_statement/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/first_party_dependencies/BUILD.in b/gazelle/python/testdata/first_party_dependencies/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/BUILD.in
diff --git a/gazelle/python/testdata/first_party_dependencies/BUILD.out b/gazelle/python/testdata/first_party_dependencies/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/BUILD.out
diff --git a/gazelle/python/testdata/first_party_dependencies/README.md b/gazelle/python/testdata/first_party_dependencies/README.md
new file mode 100644
index 0000000..f57e255
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/README.md
@@ -0,0 +1,11 @@
+# First-party dependencies
+
+There are 2 different scenarios that the extension needs to handle:
+
+1. Import statements that match sub-directory names.
+2. Import statements that don't match sub-directory names and need a hint from
+ the user via directives.
+
+This test case asserts that the generated targets cover both scenarios.
+
+With the hint we need to check if it's a .py file or a directory with `__init__.py` file.
diff --git a/gazelle/python/testdata/first_party_dependencies/WORKSPACE b/gazelle/python/testdata/first_party_dependencies/WORKSPACE
new file mode 100644
index 0000000..4959898
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/WORKSPACE
@@ -0,0 +1 @@
+# This is a test data Bazel workspace.
diff --git a/gazelle/python/testdata/first_party_dependencies/one/BUILD.in b/gazelle/python/testdata/first_party_dependencies/one/BUILD.in
new file mode 100644
index 0000000..6948b47
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_root
diff --git a/gazelle/python/testdata/first_party_dependencies/one/BUILD.out b/gazelle/python/testdata/first_party_dependencies/one/BUILD.out
new file mode 100644
index 0000000..c96a561
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/BUILD.out
@@ -0,0 +1,15 @@
+load("@rules_python//python:defs.bzl", "py_binary")
+
+# gazelle:python_root
+
+py_binary(
+ name = "one_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//one:__subpackages__"],
+ deps = [
+ "//one/bar",
+ "//one/bar/baz",
+ "//one/foo",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/__main__.py b/gazelle/python/testdata/first_party_dependencies/one/__main__.py
new file mode 100644
index 0000000..efc7900
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/__main__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import os
+
+from bar import bar
+from bar.baz import baz
+from foo import foo
+
+if __name__ == "__main__":
+ INIT_FILENAME = "__init__.py"
+ dirname = os.path.dirname(os.path.abspath(__file__))
+ assert bar() == os.path.join(dirname, "bar", INIT_FILENAME)
+ assert baz() == os.path.join(dirname, "bar", "baz", INIT_FILENAME)
+ assert foo() == os.path.join(dirname, "foo", INIT_FILENAME)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.in b/gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.in
new file mode 100644
index 0000000..7fe1f49
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.in
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "bar",
+ srcs = ["__init__.py"],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.out b/gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.out
new file mode 100644
index 0000000..470bf82
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/bar/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "bar",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/bar/__init__.py b/gazelle/python/testdata/first_party_dependencies/one/bar/__init__.py
new file mode 100644
index 0000000..d4b5fb8
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/bar/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def bar():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.in b/gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.in
new file mode 100644
index 0000000..886a89c
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.in
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "baz",
+ srcs = ["__init__.py"],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.out b/gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.out
new file mode 100644
index 0000000..a017245
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/bar/baz/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "baz",
+ srcs = ["__init__.py"],
+ imports = ["../.."],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/bar/baz/__init__.py b/gazelle/python/testdata/first_party_dependencies/one/bar/baz/__init__.py
new file mode 100644
index 0000000..5be74a7
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/bar/baz/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def baz():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.in b/gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.in
new file mode 100644
index 0000000..0ee9a30
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.in
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = ["__init__.py"],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ "//two:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.out b/gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.out
new file mode 100644
index 0000000..464fabb
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/foo/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ "//two:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/one/foo/__init__.py b/gazelle/python/testdata/first_party_dependencies/one/foo/__init__.py
new file mode 100644
index 0000000..978fb74
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/one/foo/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def foo():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/first_party_dependencies/test.yaml b/gazelle/python/testdata/first_party_dependencies/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/first_party_dependencies/three/BUILD.in b/gazelle/python/testdata/first_party_dependencies/three/BUILD.in
new file mode 100644
index 0000000..6948b47
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/three/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_root
diff --git a/gazelle/python/testdata/first_party_dependencies/three/BUILD.out b/gazelle/python/testdata/first_party_dependencies/three/BUILD.out
new file mode 100644
index 0000000..ccfb3e0
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/three/BUILD.out
@@ -0,0 +1,14 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_root
+
+py_library(
+ name = "three",
+ srcs = ["__init__.py"],
+ visibility = ["//three:__subpackages__"],
+ deps = [
+ "//one/bar",
+ "//one/bar/baz",
+ "//one/foo",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/three/__init__.py b/gazelle/python/testdata/first_party_dependencies/three/__init__.py
new file mode 100644
index 0000000..9f7d123
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/three/__init__.py
@@ -0,0 +1,24 @@
+# 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.
+
+import os
+
+from bar import bar
+from bar.baz import baz
+from foo import foo
+
+_ = os
+_ = bar
+_ = baz
+_ = foo
diff --git a/gazelle/python/testdata/first_party_dependencies/two/BUILD.in b/gazelle/python/testdata/first_party_dependencies/two/BUILD.in
new file mode 100644
index 0000000..6948b47
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/two/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_root
diff --git a/gazelle/python/testdata/first_party_dependencies/two/BUILD.out b/gazelle/python/testdata/first_party_dependencies/two/BUILD.out
new file mode 100644
index 0000000..182db08
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/two/BUILD.out
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_root
+
+py_library(
+ name = "two",
+ srcs = ["__init__.py"],
+ visibility = ["//two:__subpackages__"],
+ deps = ["//one/foo"],
+)
diff --git a/gazelle/python/testdata/first_party_dependencies/two/__init__.py b/gazelle/python/testdata/first_party_dependencies/two/__init__.py
new file mode 100644
index 0000000..88ff57b
--- /dev/null
+++ b/gazelle/python/testdata/first_party_dependencies/two/__init__.py
@@ -0,0 +1,20 @@
+# 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.
+
+import os
+
+from foo import foo
+
+_ = os
+_ = foo
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.in b/gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.in
new file mode 100644
index 0000000..fb90e4c
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.in
@@ -0,0 +1 @@
+# gazelle:resolve py foo //foo
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.out b/gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.out
new file mode 100644
index 0000000..264205b
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/BUILD.out
@@ -0,0 +1,25 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+
+# gazelle:resolve py foo //foo
+
+py_library(
+ name = "first_party_file_and_directory_modules",
+ srcs = [
+ "baz.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+py_binary(
+ name = "first_party_file_and_directory_modules_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":first_party_file_and_directory_modules",
+ "//foo",
+ "//one",
+ "//undiscoverable/package1/subpackage1",
+ ],
+)
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/README.md b/gazelle/python/testdata/first_party_file_and_directory_modules/README.md
new file mode 100644
index 0000000..2a173b4
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/README.md
@@ -0,0 +1,9 @@
+# First-party file and directory module dependencies
+
+This test case asserts that a `py_library` is generated with the dependencies
+pointing to the correct first-party target that contains a Python module file
+that was imported directly instead of a directory containing `__init__.py`.
+
+Also, it asserts that the directory with the `__init__.py` file is selected
+instead of a module file with same. E.g. `foo/__init__.py` takes precedence over
+`foo.py` when `import foo` exists.
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/WORKSPACE b/gazelle/python/testdata/first_party_file_and_directory_modules/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/__main__.py b/gazelle/python/testdata/first_party_file_and_directory_modules/__main__.py
new file mode 100644
index 0000000..242448d
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/__main__.py
@@ -0,0 +1,25 @@
+# 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.
+
+import foo
+from baz import baz as another_baz
+from foo.bar import baz
+from one.two import two
+from package1.subpackage1.module1 import find_me
+
+assert not hasattr(foo, "foo")
+assert baz() == "baz from foo/bar.py"
+assert another_baz() == "baz from baz.py"
+assert two() == "two"
+assert find_me() == "found"
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/baz.py b/gazelle/python/testdata/first_party_file_and_directory_modules/baz.py
new file mode 100644
index 0000000..e03a9ec
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/baz.py
@@ -0,0 +1,16 @@
+# 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.
+
+def baz():
+ return "baz from baz.py"
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/foo.py b/gazelle/python/testdata/first_party_file_and_directory_modules/foo.py
new file mode 100644
index 0000000..04474d8
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/foo.py
@@ -0,0 +1,16 @@
+# 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.
+
+def foo():
+ print("foo")
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.in b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.in
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.out b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.out
new file mode 100644
index 0000000..3decd90
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = [
+ "__init__.py",
+ "bar.py",
+ ],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+ deps = ["//one"],
+)
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/foo/__init__.py b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/foo/bar.py b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/bar.py
new file mode 100644
index 0000000..dacf2d4
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/foo/bar.py
@@ -0,0 +1,21 @@
+# 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.
+
+import one.two as two
+
+_ = two
+
+
+def baz():
+ return "baz from foo/bar.py"
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.in b/gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.in
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.out b/gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.out
new file mode 100644
index 0000000..7063141
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/one/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "one",
+ srcs = [
+ "__init__.py",
+ "two.py",
+ ],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/one/__init__.py b/gazelle/python/testdata/first_party_file_and_directory_modules/one/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/one/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/one/two.py b/gazelle/python/testdata/first_party_file_and_directory_modules/one/two.py
new file mode 100644
index 0000000..94cca3d
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/one/two.py
@@ -0,0 +1,16 @@
+# 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.
+
+def two():
+ return "two"
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/test.yaml b/gazelle/python/testdata/first_party_file_and_directory_modules/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.in b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.in
new file mode 100644
index 0000000..6948b47
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_root
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.out b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.out
new file mode 100644
index 0000000..6948b47
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/BUILD.out
@@ -0,0 +1 @@
+# gazelle:python_root
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.in b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.in
new file mode 100644
index 0000000..c7d0e48
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.in
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "subpackage1",
+ srcs = [
+ "__init__.py",
+ "module1.py",
+ ],
+ imports = ["../.."],
+ # Manual fix to visibility after initial generation.
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.out b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.out
new file mode 100644
index 0000000..c7d0e48
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "subpackage1",
+ srcs = [
+ "__init__.py",
+ "module1.py",
+ ],
+ imports = ["../.."],
+ # Manual fix to visibility after initial generation.
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/__init__.py b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/module1.py b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/module1.py
new file mode 100644
index 0000000..76c7227
--- /dev/null
+++ b/gazelle/python/testdata/first_party_file_and_directory_modules/undiscoverable/package1/subpackage1/module1.py
@@ -0,0 +1,16 @@
+# 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.
+
+def find_me():
+ return "found"
diff --git a/gazelle/python/testdata/from_imports/BUILD.in b/gazelle/python/testdata/from_imports/BUILD.in
new file mode 100644
index 0000000..93f2259
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_extension enabled
diff --git a/gazelle/python/testdata/from_imports/BUILD.out b/gazelle/python/testdata/from_imports/BUILD.out
new file mode 100644
index 0000000..93f2259
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/BUILD.out
@@ -0,0 +1 @@
+# gazelle:python_extension enabled
diff --git a/gazelle/python/testdata/from_imports/README.md b/gazelle/python/testdata/from_imports/README.md
new file mode 100644
index 0000000..161dd18
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/README.md
@@ -0,0 +1,7 @@
+# From Imports
+
+This test case simulates imports of the form:
+
+```python
+from foo import bar
+```
diff --git a/gazelle/python/testdata/from_imports/WORKSPACE b/gazelle/python/testdata/from_imports/WORKSPACE
new file mode 100644
index 0000000..4959898
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/WORKSPACE
@@ -0,0 +1 @@
+# This is a test data Bazel workspace.
diff --git a/gazelle/python/testdata/from_imports/foo/BUILD.in b/gazelle/python/testdata/from_imports/foo/BUILD.in
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/foo/BUILD.in
@@ -0,0 +1 @@
+
diff --git a/gazelle/python/testdata/from_imports/foo/BUILD.out b/gazelle/python/testdata/from_imports/foo/BUILD.out
new file mode 100644
index 0000000..4404d30
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/foo/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/from_imports/foo/__init__.py b/gazelle/python/testdata/from_imports/foo/__init__.py
new file mode 100644
index 0000000..d0f74a8
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/foo/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+foo = "foo"
diff --git a/gazelle/python/testdata/from_imports/foo/bar/BUILD.in b/gazelle/python/testdata/from_imports/foo/bar/BUILD.in
new file mode 100644
index 0000000..fbbec02
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/foo/bar/BUILD.in
@@ -0,0 +1,21 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_ignore_files baz.py
+
+py_library(
+ name = "baz",
+ srcs = [
+ "baz.py",
+ ],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_library(
+ name = "bar",
+ srcs = [
+ "__init__.py",
+ ],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/foo/bar/BUILD.out b/gazelle/python/testdata/from_imports/foo/bar/BUILD.out
new file mode 100644
index 0000000..fbbec02
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/foo/bar/BUILD.out
@@ -0,0 +1,21 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_ignore_files baz.py
+
+py_library(
+ name = "baz",
+ srcs = [
+ "baz.py",
+ ],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_library(
+ name = "bar",
+ srcs = [
+ "__init__.py",
+ ],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/foo/bar/__init__.py b/gazelle/python/testdata/from_imports/foo/bar/__init__.py
new file mode 100644
index 0000000..240f382
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/foo/bar/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+bar = "bar"
diff --git a/gazelle/python/testdata/from_imports/foo/bar/baz.py b/gazelle/python/testdata/from_imports/foo/bar/baz.py
new file mode 100644
index 0000000..9aeae61
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/foo/bar/baz.py
@@ -0,0 +1,15 @@
+# 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.
+
+baz = "baz"
diff --git a/gazelle/python/testdata/from_imports/gazelle_python.yaml b/gazelle/python/testdata/from_imports/gazelle_python.yaml
new file mode 100644
index 0000000..132854e
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/gazelle_python.yaml
@@ -0,0 +1,19 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: rootboto3
+ boto4: rootboto4
+ pip_deps_repository_name: root_pip_deps
diff --git a/gazelle/python/testdata/from_imports/import_from_init_py/BUILD.in b/gazelle/python/testdata/from_imports/import_from_init_py/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_from_init_py/BUILD.in
diff --git a/gazelle/python/testdata/from_imports/import_from_init_py/BUILD.out b/gazelle/python/testdata/from_imports/import_from_init_py/BUILD.out
new file mode 100644
index 0000000..99b4861
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_from_init_py/BUILD.out
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "import_from_init_py",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+ deps = ["//foo/bar"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/import_from_init_py/__init__.py b/gazelle/python/testdata/from_imports/import_from_init_py/__init__.py
new file mode 100644
index 0000000..bd6d8a5
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_from_init_py/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# bar is a variable inside foo/bar/__init__.py
+from foo.bar import bar
diff --git a/gazelle/python/testdata/from_imports/import_from_multiple/BUILD.in b/gazelle/python/testdata/from_imports/import_from_multiple/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_from_multiple/BUILD.in
diff --git a/gazelle/python/testdata/from_imports/import_from_multiple/BUILD.out b/gazelle/python/testdata/from_imports/import_from_multiple/BUILD.out
new file mode 100644
index 0000000..d8219bb
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_from_multiple/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "import_from_multiple",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//foo/bar",
+ "//foo/bar:baz",
+ ],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/import_from_multiple/__init__.py b/gazelle/python/testdata/from_imports/import_from_multiple/__init__.py
new file mode 100644
index 0000000..05cd104
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_from_multiple/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# Import multiple values from the same import.
+from foo.bar import bar, baz
diff --git a/gazelle/python/testdata/from_imports/import_nested_file/BUILD.in b/gazelle/python/testdata/from_imports/import_nested_file/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_file/BUILD.in
diff --git a/gazelle/python/testdata/from_imports/import_nested_file/BUILD.out b/gazelle/python/testdata/from_imports/import_nested_file/BUILD.out
new file mode 100644
index 0000000..662da9c
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_file/BUILD.out
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "import_nested_file",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+ deps = ["//foo/bar:baz"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/import_nested_file/__init__.py b/gazelle/python/testdata/from_imports/import_nested_file/__init__.py
new file mode 100644
index 0000000..55a1621
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_file/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# baz.py is a file at foo/bar/baz.py
+from foo.bar import baz
diff --git a/gazelle/python/testdata/from_imports/import_nested_module/BUILD.in b/gazelle/python/testdata/from_imports/import_nested_module/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_module/BUILD.in
diff --git a/gazelle/python/testdata/from_imports/import_nested_module/BUILD.out b/gazelle/python/testdata/from_imports/import_nested_module/BUILD.out
new file mode 100644
index 0000000..ec6da50
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_module/BUILD.out
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "import_nested_module",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+ deps = ["//foo/bar"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/import_nested_module/__init__.py b/gazelle/python/testdata/from_imports/import_nested_module/__init__.py
new file mode 100644
index 0000000..96fa0e5
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_module/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# bar is a module at foo/bar/__init__.py
+from foo import bar
diff --git a/gazelle/python/testdata/from_imports/import_nested_var/BUILD.in b/gazelle/python/testdata/from_imports/import_nested_var/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_var/BUILD.in
diff --git a/gazelle/python/testdata/from_imports/import_nested_var/BUILD.out b/gazelle/python/testdata/from_imports/import_nested_var/BUILD.out
new file mode 100644
index 0000000..8ee527e
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_var/BUILD.out
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "import_nested_var",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+ deps = ["//foo/bar:baz"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/import_nested_var/__init__.py b/gazelle/python/testdata/from_imports/import_nested_var/__init__.py
new file mode 100644
index 0000000..d0f51c4
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_nested_var/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# baz is a variable in foo/bar/baz.py
+from foo.bar.baz import baz
diff --git a/gazelle/python/testdata/from_imports/import_top_level_var/BUILD.in b/gazelle/python/testdata/from_imports/import_top_level_var/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_top_level_var/BUILD.in
diff --git a/gazelle/python/testdata/from_imports/import_top_level_var/BUILD.out b/gazelle/python/testdata/from_imports/import_top_level_var/BUILD.out
new file mode 100644
index 0000000..6b584d7
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_top_level_var/BUILD.out
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "import_top_level_var",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+ deps = ["//foo"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/import_top_level_var/__init__.py b/gazelle/python/testdata/from_imports/import_top_level_var/__init__.py
new file mode 100644
index 0000000..71dd7c4
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/import_top_level_var/__init__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# foo is a variable in foo/__init__.py
+from foo import foo
diff --git a/gazelle/python/testdata/from_imports/std_module/BUILD.in b/gazelle/python/testdata/from_imports/std_module/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/std_module/BUILD.in
diff --git a/gazelle/python/testdata/from_imports/std_module/BUILD.out b/gazelle/python/testdata/from_imports/std_module/BUILD.out
new file mode 100644
index 0000000..4903999
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/std_module/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "std_module",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+) \ No newline at end of file
diff --git a/gazelle/python/testdata/from_imports/std_module/__init__.py b/gazelle/python/testdata/from_imports/std_module/__init__.py
new file mode 100644
index 0000000..5518cc0
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/std_module/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+# Gazelle should recognize this from import
+# as the standard module __future__.
+from __future__ import print_function
diff --git a/gazelle/python/testdata/from_imports/test.yaml b/gazelle/python/testdata/from_imports/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/from_imports/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/generated_test_entrypoint/BUILD.in b/gazelle/python/testdata/generated_test_entrypoint/BUILD.in
new file mode 100644
index 0000000..06616fb
--- /dev/null
+++ b/gazelle/python/testdata/generated_test_entrypoint/BUILD.in
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+something(
+ name = "__test__",
+)
+
+py_library(
+ name = "generated_test_entrypoint",
+ srcs = ["__init__.py"],
+)
diff --git a/gazelle/python/testdata/generated_test_entrypoint/BUILD.out b/gazelle/python/testdata/generated_test_entrypoint/BUILD.out
new file mode 100644
index 0000000..e8e304c
--- /dev/null
+++ b/gazelle/python/testdata/generated_test_entrypoint/BUILD.out
@@ -0,0 +1,21 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+something(
+ name = "__test__",
+)
+
+py_library(
+ name = "generated_test_entrypoint",
+ srcs = [
+ "__init__.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "generated_test_entrypoint_test",
+ srcs = [":__test__"],
+ main = ":__test__.py",
+ deps = [":__test__"],
+)
diff --git a/gazelle/python/testdata/generated_test_entrypoint/README.md b/gazelle/python/testdata/generated_test_entrypoint/README.md
new file mode 100644
index 0000000..69f8415
--- /dev/null
+++ b/gazelle/python/testdata/generated_test_entrypoint/README.md
@@ -0,0 +1,4 @@
+# Generated test entrypoint
+
+This test case asserts that a `py_test` is generated using a target named
+`__test__` as its `main` entrypoint.
diff --git a/gazelle/python/testdata/generated_test_entrypoint/WORKSPACE b/gazelle/python/testdata/generated_test_entrypoint/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/generated_test_entrypoint/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/generated_test_entrypoint/__init__.py b/gazelle/python/testdata/generated_test_entrypoint/__init__.py
new file mode 100644
index 0000000..b274b0d
--- /dev/null
+++ b/gazelle/python/testdata/generated_test_entrypoint/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+from foo import foo
+
+_ = foo
diff --git a/gazelle/python/testdata/generated_test_entrypoint/foo.py b/gazelle/python/testdata/generated_test_entrypoint/foo.py
new file mode 100644
index 0000000..932de45
--- /dev/null
+++ b/gazelle/python/testdata/generated_test_entrypoint/foo.py
@@ -0,0 +1,16 @@
+# 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.
+
+def foo():
+ return "foo"
diff --git a/gazelle/python/testdata/generated_test_entrypoint/test.yaml b/gazelle/python/testdata/generated_test_entrypoint/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/generated_test_entrypoint/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/ignored_invalid_imported_module/BUILD.in b/gazelle/python/testdata/ignored_invalid_imported_module/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/ignored_invalid_imported_module/BUILD.in
diff --git a/gazelle/python/testdata/ignored_invalid_imported_module/BUILD.out b/gazelle/python/testdata/ignored_invalid_imported_module/BUILD.out
new file mode 100644
index 0000000..b8c936a
--- /dev/null
+++ b/gazelle/python/testdata/ignored_invalid_imported_module/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "ignored_invalid_imported_module",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test_foo//:pkg"],
+)
diff --git a/gazelle/python/testdata/ignored_invalid_imported_module/README.md b/gazelle/python/testdata/ignored_invalid_imported_module/README.md
new file mode 100644
index 0000000..55dcc9b
--- /dev/null
+++ b/gazelle/python/testdata/ignored_invalid_imported_module/README.md
@@ -0,0 +1,3 @@
+# Ignored invalid imported module
+
+This test case asserts that the module's validation step succeeds as expected.
diff --git a/gazelle/python/testdata/ignored_invalid_imported_module/WORKSPACE b/gazelle/python/testdata/ignored_invalid_imported_module/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/ignored_invalid_imported_module/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/ignored_invalid_imported_module/__init__.py b/gazelle/python/testdata/ignored_invalid_imported_module/__init__.py
new file mode 100644
index 0000000..a094ed0
--- /dev/null
+++ b/gazelle/python/testdata/ignored_invalid_imported_module/__init__.py
@@ -0,0 +1,36 @@
+# 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.
+
+# gazelle:ignore abcdefg1,abcdefg2
+# gazelle:ignore abcdefg3
+
+import abcdefg1
+import abcdefg2
+import abcdefg3
+import foo
+
+_ = abcdefg1
+_ = abcdefg2
+_ = abcdefg3
+_ = foo
+
+try:
+ # gazelle:ignore grpc
+ import grpc
+
+ grpc_available = True
+except ImportError:
+ grpc_available = False
+
+_ = grpc
diff --git a/gazelle/python/testdata/ignored_invalid_imported_module/gazelle_python.yaml b/gazelle/python/testdata/ignored_invalid_imported_module/gazelle_python.yaml
new file mode 100644
index 0000000..4b12372
--- /dev/null
+++ b/gazelle/python/testdata/ignored_invalid_imported_module/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ foo: foo
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/ignored_invalid_imported_module/test.yaml b/gazelle/python/testdata/ignored_invalid_imported_module/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/ignored_invalid_imported_module/test.yaml
@@ -0,0 +1,17 @@
+# 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.
+
+---
+expect:
+ exit_code: 0
diff --git a/gazelle/python/testdata/invalid_annotation/BUILD.in b/gazelle/python/testdata/invalid_annotation/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation/BUILD.in
diff --git a/gazelle/python/testdata/invalid_annotation/BUILD.out b/gazelle/python/testdata/invalid_annotation/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation/BUILD.out
diff --git a/gazelle/python/testdata/invalid_annotation/README.md b/gazelle/python/testdata/invalid_annotation/README.md
new file mode 100644
index 0000000..b2544b5
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation/README.md
@@ -0,0 +1,2 @@
+# Invalid annotation
+This test case asserts that the parse step fails as expected due to invalid annotation format.
diff --git a/gazelle/python/testdata/invalid_annotation/WORKSPACE b/gazelle/python/testdata/invalid_annotation/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/invalid_annotation/__init__.py b/gazelle/python/testdata/invalid_annotation/__init__.py
new file mode 100644
index 0000000..7aee876
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# gazelle:ignore
diff --git a/gazelle/python/testdata/invalid_annotation/test.yaml b/gazelle/python/testdata/invalid_annotation/test.yaml
new file mode 100644
index 0000000..19924b1
--- /dev/null
+++ b/gazelle/python/testdata/invalid_annotation/test.yaml
@@ -0,0 +1,19 @@
+# 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.
+
+---
+expect:
+ exit_code: 1
+ stderr: |
+ gazelle: ERROR: failed to parse annotations: `# gazelle:ignore` requires a value
diff --git a/gazelle/python/testdata/invalid_imported_module/BUILD.in b/gazelle/python/testdata/invalid_imported_module/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_imported_module/BUILD.in
diff --git a/gazelle/python/testdata/invalid_imported_module/BUILD.out b/gazelle/python/testdata/invalid_imported_module/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/invalid_imported_module/BUILD.out
diff --git a/gazelle/python/testdata/invalid_imported_module/README.md b/gazelle/python/testdata/invalid_imported_module/README.md
new file mode 100644
index 0000000..85e6f45
--- /dev/null
+++ b/gazelle/python/testdata/invalid_imported_module/README.md
@@ -0,0 +1,3 @@
+# Invalid imported module
+
+This test case asserts that the module's validation step fails as expected.
diff --git a/gazelle/python/testdata/invalid_imported_module/WORKSPACE b/gazelle/python/testdata/invalid_imported_module/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/invalid_imported_module/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/invalid_imported_module/__init__.py b/gazelle/python/testdata/invalid_imported_module/__init__.py
new file mode 100644
index 0000000..dc6fb85
--- /dev/null
+++ b/gazelle/python/testdata/invalid_imported_module/__init__.py
@@ -0,0 +1,22 @@
+# 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.
+
+try:
+ import grpc
+
+ grpc_available = True
+except ImportError:
+ grpc_available = False
+
+_ = grpc
diff --git a/gazelle/python/testdata/invalid_imported_module/test.yaml b/gazelle/python/testdata/invalid_imported_module/test.yaml
new file mode 100644
index 0000000..6bcea39
--- /dev/null
+++ b/gazelle/python/testdata/invalid_imported_module/test.yaml
@@ -0,0 +1,22 @@
+# 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.
+
+---
+expect:
+ exit_code: 1
+ stderr: |
+ gazelle: ERROR: failed to validate dependencies for target "//:invalid_imported_module": "grpc" at line 16 from "__init__.py" is an invalid dependency: possible solutions:
+ 1. Add it as a dependency in the requirements.txt file.
+ 2. Instruct Gazelle to resolve to a known dependency using the gazelle:resolve directive.
+ 3. Ignore it with a comment '# gazelle:ignore grpc' in the Python file.
diff --git a/gazelle/python/testdata/monorepo/BUILD.in b/gazelle/python/testdata/monorepo/BUILD.in
new file mode 100644
index 0000000..adc9e83
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_extension disabled
diff --git a/gazelle/python/testdata/monorepo/BUILD.out b/gazelle/python/testdata/monorepo/BUILD.out
new file mode 100644
index 0000000..adc9e83
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/BUILD.out
@@ -0,0 +1 @@
+# gazelle:python_extension disabled
diff --git a/gazelle/python/testdata/monorepo/README.md b/gazelle/python/testdata/monorepo/README.md
new file mode 100644
index 0000000..b3ac3d2
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/README.md
@@ -0,0 +1,4 @@
+# Monorepo
+
+This test case focuses on having multiple configurations tweaked in combination
+to simulate a monorepo.
diff --git a/gazelle/python/testdata/monorepo/WORKSPACE b/gazelle/python/testdata/monorepo/WORKSPACE
new file mode 100644
index 0000000..4959898
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/WORKSPACE
@@ -0,0 +1 @@
+# This is a test data Bazel workspace.
diff --git a/gazelle/python/testdata/monorepo/a/BUILD.in b/gazelle/python/testdata/monorepo/a/BUILD.in
new file mode 100644
index 0000000..265129e
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/a/BUILD.in
@@ -0,0 +1 @@
+# gazelle:exclude bar/baz/hue.py \ No newline at end of file
diff --git a/gazelle/python/testdata/monorepo/a/BUILD.out b/gazelle/python/testdata/monorepo/a/BUILD.out
new file mode 100644
index 0000000..265129e
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/a/BUILD.out
@@ -0,0 +1 @@
+# gazelle:exclude bar/baz/hue.py \ No newline at end of file
diff --git a/gazelle/python/testdata/monorepo/a/README.md b/gazelle/python/testdata/monorepo/a/README.md
new file mode 100644
index 0000000..84d3bff
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/a/README.md
@@ -0,0 +1,3 @@
+# Exclusions
+* Intentionally make the directory "a" so Gazelle visit this before "coarse_grained"
+* Making sure that the exclusion here doesn't affect coarse_grained/bar/baz/hue.py \ No newline at end of file
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/BUILD.in b/gazelle/python/testdata/monorepo/coarse_grained/BUILD.in
new file mode 100644
index 0000000..b85b321
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/BUILD.in
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_extension enabled
+# gazelle:python_root
+# gazelle:python_generation_mode project
+
+# gazelle:exclude bar/baz/*_excluded.py
+
+py_library(
+ name = "coarse_grained",
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/BUILD.out b/gazelle/python/testdata/monorepo/coarse_grained/BUILD.out
new file mode 100644
index 0000000..b11cbbd
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/BUILD.out
@@ -0,0 +1,20 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_extension enabled
+# gazelle:python_root
+# gazelle:python_generation_mode project
+
+# gazelle:exclude bar/baz/*_excluded.py
+
+py_library(
+ name = "coarse_grained",
+ srcs = [
+ "__init__.py",
+ "bar/__init__.py",
+ "bar/baz/__init__.py",
+ "bar/baz/hue.py",
+ "foo/__init__.py",
+ ],
+ visibility = ["//:__subpackages__"],
+ deps = ["@root_pip_deps_rootboto3//:pkg"],
+)
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/__init__.py b/gazelle/python/testdata/monorepo/coarse_grained/__init__.py
new file mode 100644
index 0000000..6e77327
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/__init__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import os
+
+import boto3
+from bar import bar
+from bar.baz import baz
+from foo import foo
+
+_ = os
+_ = boto3
+_ = bar
+_ = baz
+_ = foo
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.in b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.in
new file mode 100644
index 0000000..421b486
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_generation_mode package
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.out b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.out
new file mode 100644
index 0000000..837e59f
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/BUILD.out
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_generation_mode package
+
+py_library(
+ name = "_boundary",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//coarse_grained:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/_boundary/README.md b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/README.md
new file mode 100644
index 0000000..0e67695
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/README.md
@@ -0,0 +1,5 @@
+# \_boundary
+
+This Bazel package must be before other packages in the `coarse_grained`
+directory so that we assert that walking the tree still happens after ignoring
+this package from the parent coarse-grained generation.
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/_boundary/__init__.py b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/__init__.py
new file mode 100644
index 0000000..bbdfb4c
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/_boundary/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/bar/__init__.py b/gazelle/python/testdata/monorepo/coarse_grained/bar/__init__.py
new file mode 100644
index 0000000..499a090
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/bar/__init__.py
@@ -0,0 +1,23 @@
+# 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.
+
+import os
+
+import boto3
+
+_ = boto3
+
+
+def bar():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/__init__.py b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/__init__.py
new file mode 100644
index 0000000..5be74a7
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def baz():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/first_excluded.py b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/first_excluded.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/first_excluded.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/hue.py b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/hue.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/hue.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/second_excluded.py b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/second_excluded.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/bar/baz/second_excluded.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/monorepo/coarse_grained/foo/__init__.py b/gazelle/python/testdata/monorepo/coarse_grained/foo/__init__.py
new file mode 100644
index 0000000..978fb74
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/coarse_grained/foo/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def foo():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/gazelle_python.yaml b/gazelle/python/testdata/monorepo/gazelle_python.yaml
new file mode 100644
index 0000000..132854e
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/gazelle_python.yaml
@@ -0,0 +1,19 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: rootboto3
+ boto4: rootboto4
+ pip_deps_repository_name: root_pip_deps
diff --git a/gazelle/python/testdata/monorepo/one/BUILD.in b/gazelle/python/testdata/monorepo/one/BUILD.in
new file mode 100644
index 0000000..b11b373
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/BUILD.in
@@ -0,0 +1,2 @@
+# gazelle:python_extension enabled
+# gazelle:python_root
diff --git a/gazelle/python/testdata/monorepo/one/BUILD.out b/gazelle/python/testdata/monorepo/one/BUILD.out
new file mode 100644
index 0000000..5098cc9
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/BUILD.out
@@ -0,0 +1,17 @@
+load("@rules_python//python:defs.bzl", "py_binary")
+
+# gazelle:python_extension enabled
+# gazelle:python_root
+
+py_binary(
+ name = "one_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//one:__subpackages__"],
+ deps = [
+ "//one/bar",
+ "//one/bar/baz:modified_name_baz",
+ "//one/foo",
+ "@one_pip_deps_oneboto3//:pkg",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/one/__main__.py b/gazelle/python/testdata/monorepo/one/__main__.py
new file mode 100644
index 0000000..7ef50cc
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/__main__.py
@@ -0,0 +1,29 @@
+# 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.
+
+import os
+
+import boto3
+from bar import bar
+from bar.baz import baz
+from foo import foo
+
+_ = boto3
+
+if __name__ == "__main__":
+ INIT_FILENAME = "__init__.py"
+ dirname = os.path.dirname(os.path.abspath(__file__))
+ assert bar() == os.path.join(dirname, "bar", INIT_FILENAME)
+ assert baz() == os.path.join(dirname, "bar", "baz", INIT_FILENAME)
+ assert foo() == os.path.join(dirname, "foo", INIT_FILENAME)
diff --git a/gazelle/python/testdata/monorepo/one/bar/BUILD.in b/gazelle/python/testdata/monorepo/one/bar/BUILD.in
new file mode 100644
index 0000000..7fe1f49
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/bar/BUILD.in
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "bar",
+ srcs = ["__init__.py"],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/one/bar/BUILD.out b/gazelle/python/testdata/monorepo/one/bar/BUILD.out
new file mode 100644
index 0000000..6ee6515
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/bar/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "bar",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+ deps = ["@one_pip_deps_oneboto3//:pkg"],
+)
diff --git a/gazelle/python/testdata/monorepo/one/bar/__init__.py b/gazelle/python/testdata/monorepo/one/bar/__init__.py
new file mode 100644
index 0000000..499a090
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/bar/__init__.py
@@ -0,0 +1,23 @@
+# 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.
+
+import os
+
+import boto3
+
+_ = boto3
+
+
+def bar():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/one/bar/baz/BUILD.in b/gazelle/python/testdata/monorepo/one/bar/baz/BUILD.in
new file mode 100644
index 0000000..00ba8ed
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/bar/baz/BUILD.in
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "modified_name_baz",
+ srcs = ["__init__.py"],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/one/bar/baz/BUILD.out b/gazelle/python/testdata/monorepo/one/bar/baz/BUILD.out
new file mode 100644
index 0000000..1eb52fc
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/bar/baz/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "modified_name_baz",
+ srcs = ["__init__.py"],
+ imports = ["../.."],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/one/bar/baz/__init__.py b/gazelle/python/testdata/monorepo/one/bar/baz/__init__.py
new file mode 100644
index 0000000..5be74a7
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/bar/baz/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def baz():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/one/foo/BUILD.in b/gazelle/python/testdata/monorepo/one/foo/BUILD.in
new file mode 100644
index 0000000..0ee9a30
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/foo/BUILD.in
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = ["__init__.py"],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ "//two:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/one/foo/BUILD.out b/gazelle/python/testdata/monorepo/one/foo/BUILD.out
new file mode 100644
index 0000000..464fabb
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/foo/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = [
+ "//one:__subpackages__",
+ "//three:__subpackages__",
+ "//two:__subpackages__",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/one/foo/__init__.py b/gazelle/python/testdata/monorepo/one/foo/__init__.py
new file mode 100644
index 0000000..978fb74
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/foo/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def foo():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/one/gazelle_python.yaml b/gazelle/python/testdata/monorepo/one/gazelle_python.yaml
new file mode 100644
index 0000000..6b323b7
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/one/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: oneboto3
+ pip_deps_repository_name: one_pip_deps
diff --git a/gazelle/python/testdata/monorepo/test.yaml b/gazelle/python/testdata/monorepo/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/monorepo/three/BUILD.in b/gazelle/python/testdata/monorepo/three/BUILD.in
new file mode 100644
index 0000000..79bb63f
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/three/BUILD.in
@@ -0,0 +1,5 @@
+# gazelle:python_extension enabled
+# gazelle:python_root
+# gazelle:resolve py bar //one/bar
+# gazelle:resolve py bar.baz //one/bar/baz:modified_name_baz
+# gazelle:resolve py foo //one/foo
diff --git a/gazelle/python/testdata/monorepo/three/BUILD.out b/gazelle/python/testdata/monorepo/three/BUILD.out
new file mode 100644
index 0000000..78a3927
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/three/BUILD.out
@@ -0,0 +1,21 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_extension enabled
+# gazelle:python_root
+# gazelle:resolve py bar //one/bar
+# gazelle:resolve py bar.baz //one/bar/baz:modified_name_baz
+# gazelle:resolve py foo //one/foo
+
+py_library(
+ name = "three",
+ srcs = ["__init__.py"],
+ visibility = ["//three:__subpackages__"],
+ deps = [
+ "//coarse_grained",
+ "//one/bar",
+ "//one/bar/baz:modified_name_baz",
+ "//one/foo",
+ "@root_pip_deps_rootboto4//:pkg",
+ "@three_pip_deps_threeboto3//:pkg",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/three/__init__.py b/gazelle/python/testdata/monorepo/three/__init__.py
new file mode 100644
index 0000000..b324b0c
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/three/__init__.py
@@ -0,0 +1,30 @@
+# 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.
+
+import os
+
+import bar.baz.hue as hue
+import boto3
+import boto4
+from bar import bar
+from bar.baz import baz
+from foo import foo
+
+_ = os
+_ = boto3
+_ = boto4
+_ = bar
+_ = baz
+_ = foo
+_ = hue
diff --git a/gazelle/python/testdata/monorepo/three/gazelle_python.yaml b/gazelle/python/testdata/monorepo/three/gazelle_python.yaml
new file mode 100644
index 0000000..8280b38
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/three/gazelle_python.yaml
@@ -0,0 +1,19 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: threeboto3
+ pip_repository:
+ name: three_pip_deps
diff --git a/gazelle/python/testdata/monorepo/two/BUILD.in b/gazelle/python/testdata/monorepo/two/BUILD.in
new file mode 100644
index 0000000..31812e0
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/two/BUILD.in
@@ -0,0 +1,3 @@
+# gazelle:python_extension enabled
+# gazelle:python_root
+# gazelle:resolve py foo //one/foo
diff --git a/gazelle/python/testdata/monorepo/two/BUILD.out b/gazelle/python/testdata/monorepo/two/BUILD.out
new file mode 100644
index 0000000..9cda007
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/two/BUILD.out
@@ -0,0 +1,15 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_extension enabled
+# gazelle:python_root
+# gazelle:resolve py foo //one/foo
+
+py_library(
+ name = "two",
+ srcs = ["__init__.py"],
+ visibility = ["//two:__subpackages__"],
+ deps = [
+ "//one/foo",
+ "@two_pip_deps_twoboto3//:pkg",
+ ],
+)
diff --git a/gazelle/python/testdata/monorepo/two/__init__.py b/gazelle/python/testdata/monorepo/two/__init__.py
new file mode 100644
index 0000000..d080c27
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/two/__init__.py
@@ -0,0 +1,22 @@
+# 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.
+
+import os
+
+import boto3
+from foo import foo
+
+_ = os
+_ = boto3
+_ = foo
diff --git a/gazelle/python/testdata/monorepo/two/gazelle_python.yaml b/gazelle/python/testdata/monorepo/two/gazelle_python.yaml
new file mode 100644
index 0000000..88c24d0
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/two/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: twoboto3
+ pip_deps_repository_name: two_pip_deps
diff --git a/gazelle/python/testdata/monorepo/wont_generate/BUILD.in b/gazelle/python/testdata/monorepo/wont_generate/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/BUILD.in
diff --git a/gazelle/python/testdata/monorepo/wont_generate/BUILD.out b/gazelle/python/testdata/monorepo/wont_generate/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/BUILD.out
diff --git a/gazelle/python/testdata/monorepo/wont_generate/__main__.py b/gazelle/python/testdata/monorepo/wont_generate/__main__.py
new file mode 100644
index 0000000..efc7900
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/__main__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import os
+
+from bar import bar
+from bar.baz import baz
+from foo import foo
+
+if __name__ == "__main__":
+ INIT_FILENAME = "__init__.py"
+ dirname = os.path.dirname(os.path.abspath(__file__))
+ assert bar() == os.path.join(dirname, "bar", INIT_FILENAME)
+ assert baz() == os.path.join(dirname, "bar", "baz", INIT_FILENAME)
+ assert foo() == os.path.join(dirname, "foo", INIT_FILENAME)
diff --git a/gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.in b/gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.in
diff --git a/gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.out b/gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/bar/BUILD.out
diff --git a/gazelle/python/testdata/monorepo/wont_generate/bar/__init__.py b/gazelle/python/testdata/monorepo/wont_generate/bar/__init__.py
new file mode 100644
index 0000000..d4b5fb8
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/bar/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def bar():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.in b/gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.in
diff --git a/gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.out b/gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/bar/baz/BUILD.out
diff --git a/gazelle/python/testdata/monorepo/wont_generate/bar/baz/__init__.py b/gazelle/python/testdata/monorepo/wont_generate/bar/baz/__init__.py
new file mode 100644
index 0000000..5be74a7
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/bar/baz/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def baz():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.in b/gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.in
diff --git a/gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.out b/gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/foo/BUILD.out
diff --git a/gazelle/python/testdata/monorepo/wont_generate/foo/__init__.py b/gazelle/python/testdata/monorepo/wont_generate/foo/__init__.py
new file mode 100644
index 0000000..978fb74
--- /dev/null
+++ b/gazelle/python/testdata/monorepo/wont_generate/foo/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+
+
+def foo():
+ return os.path.abspath(__file__)
diff --git a/gazelle/python/testdata/multiple_tests/BUILD.in b/gazelle/python/testdata/multiple_tests/BUILD.in
new file mode 100644
index 0000000..9e84e5d
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/BUILD.in
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "multiple_tests",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "bar_test",
+ srcs = ["bar_test.py"],
+)
diff --git a/gazelle/python/testdata/multiple_tests/BUILD.out b/gazelle/python/testdata/multiple_tests/BUILD.out
new file mode 100644
index 0000000..fd67724
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/BUILD.out
@@ -0,0 +1,17 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "multiple_tests",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "bar_test",
+ srcs = ["bar_test.py"],
+)
+
+py_test(
+ name = "foo_test",
+ srcs = ["foo_test.py"],
+)
diff --git a/gazelle/python/testdata/multiple_tests/README.md b/gazelle/python/testdata/multiple_tests/README.md
new file mode 100644
index 0000000..8220f61
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/README.md
@@ -0,0 +1,3 @@
+# Multiple tests
+
+This test case asserts that a second `py_test` rule is correctly created when a second `*_test.py` file is added to a package with an existing `py_test` rule.
diff --git a/gazelle/python/testdata/multiple_tests/WORKSPACE b/gazelle/python/testdata/multiple_tests/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/multiple_tests/__init__.py b/gazelle/python/testdata/multiple_tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/__init__.py
diff --git a/gazelle/python/testdata/multiple_tests/bar_test.py b/gazelle/python/testdata/multiple_tests/bar_test.py
new file mode 100644
index 0000000..9948f1c
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/bar_test.py
@@ -0,0 +1,24 @@
+# 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.
+
+import unittest
+
+
+class BarTest(unittest.TestCase):
+ def test_foo(self):
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gazelle/python/testdata/multiple_tests/foo_test.py b/gazelle/python/testdata/multiple_tests/foo_test.py
new file mode 100644
index 0000000..a128adf
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/foo_test.py
@@ -0,0 +1,24 @@
+# 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.
+
+import unittest
+
+
+class FooTest(unittest.TestCase):
+ def test_foo(self):
+ pass
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gazelle/python/testdata/multiple_tests/test.yaml b/gazelle/python/testdata/multiple_tests/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/multiple_tests/test.yaml
@@ -0,0 +1,17 @@
+# 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.
+
+---
+expect:
+ exit_code: 0
diff --git a/gazelle/python/testdata/naming_convention/BUILD.in b/gazelle/python/testdata/naming_convention/BUILD.in
new file mode 100644
index 0000000..7517848
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/BUILD.in
@@ -0,0 +1,3 @@
+# gazelle:python_library_naming_convention my_$package_name$_library
+# gazelle:python_binary_naming_convention my_$package_name$_binary
+# gazelle:python_test_naming_convention my_$package_name$_test
diff --git a/gazelle/python/testdata/naming_convention/BUILD.out b/gazelle/python/testdata/naming_convention/BUILD.out
new file mode 100644
index 0000000..e2f0674
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/BUILD.out
@@ -0,0 +1,26 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+
+# gazelle:python_library_naming_convention my_$package_name$_library
+# gazelle:python_binary_naming_convention my_$package_name$_binary
+# gazelle:python_test_naming_convention my_$package_name$_test
+
+py_library(
+ name = "my_naming_convention_library",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_binary(
+ name = "my_naming_convention_binary",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [":my_naming_convention_library"],
+)
+
+py_test(
+ name = "my_naming_convention_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":my_naming_convention_library"],
+)
diff --git a/gazelle/python/testdata/naming_convention/README.md b/gazelle/python/testdata/naming_convention/README.md
new file mode 100644
index 0000000..9dd88ec
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/README.md
@@ -0,0 +1,4 @@
+# Naming convention
+
+This test case asserts that py\_{library,binary,test} targets are generated
+correctly based on the directives that control their naming conventions.
diff --git a/gazelle/python/testdata/naming_convention/WORKSPACE b/gazelle/python/testdata/naming_convention/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/naming_convention/__init__.py b/gazelle/python/testdata/naming_convention/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/naming_convention/__main__.py b/gazelle/python/testdata/naming_convention/__main__.py
new file mode 100644
index 0000000..a3afc79
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/__main__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import __init__ \ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/__test__.py b/gazelle/python/testdata/naming_convention/__test__.py
new file mode 100644
index 0000000..a3afc79
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/__test__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import __init__ \ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/dont_rename/BUILD.in b/gazelle/python/testdata/naming_convention/dont_rename/BUILD.in
new file mode 100644
index 0000000..8d2ae35
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/dont_rename/BUILD.in
@@ -0,0 +1,7 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+
+py_library(
+ name = "dont_rename",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/naming_convention/dont_rename/BUILD.out b/gazelle/python/testdata/naming_convention/dont_rename/BUILD.out
new file mode 100644
index 0000000..4d4ead8
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/dont_rename/BUILD.out
@@ -0,0 +1,25 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+
+py_library(
+ name = "dont_rename",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_binary(
+ name = "my_dont_rename_binary",
+ srcs = ["__main__.py"],
+ imports = [".."],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [":dont_rename"],
+)
+
+py_test(
+ name = "my_dont_rename_test",
+ srcs = ["__test__.py"],
+ imports = [".."],
+ main = "__test__.py",
+ deps = [":dont_rename"],
+)
diff --git a/gazelle/python/testdata/naming_convention/dont_rename/__init__.py b/gazelle/python/testdata/naming_convention/dont_rename/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/dont_rename/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/naming_convention/dont_rename/__main__.py b/gazelle/python/testdata/naming_convention/dont_rename/__main__.py
new file mode 100644
index 0000000..a3afc79
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/dont_rename/__main__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import __init__ \ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/dont_rename/__test__.py b/gazelle/python/testdata/naming_convention/dont_rename/__test__.py
new file mode 100644
index 0000000..a3afc79
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/dont_rename/__test__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import __init__ \ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.in b/gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.in
new file mode 100644
index 0000000..c81e735
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.in
@@ -0,0 +1,5 @@
+go_library(name = "resolve_conflict")
+
+go_binary(name = "resolve_conflict_bin")
+
+go_test(name = "resolve_conflict_test")
diff --git a/gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.out b/gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.out
new file mode 100644
index 0000000..3fa5de2
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/resolve_conflict/BUILD.out
@@ -0,0 +1,31 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
+
+go_library(name = "resolve_conflict")
+
+go_binary(name = "resolve_conflict_bin")
+
+go_test(name = "resolve_conflict_test")
+
+py_library(
+ name = "my_resolve_conflict_library",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_binary(
+ name = "my_resolve_conflict_binary",
+ srcs = ["__main__.py"],
+ imports = [".."],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [":my_resolve_conflict_library"],
+)
+
+py_test(
+ name = "my_resolve_conflict_test",
+ srcs = ["__test__.py"],
+ imports = [".."],
+ main = "__test__.py",
+ deps = [":my_resolve_conflict_library"],
+)
diff --git a/gazelle/python/testdata/naming_convention/resolve_conflict/__init__.py b/gazelle/python/testdata/naming_convention/resolve_conflict/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/resolve_conflict/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py b/gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py
new file mode 100644
index 0000000..a3afc79
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/resolve_conflict/__main__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import __init__ \ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py b/gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py
new file mode 100644
index 0000000..a3afc79
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/resolve_conflict/__test__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import __init__ \ No newline at end of file
diff --git a/gazelle/python/testdata/naming_convention/test.yaml b/gazelle/python/testdata/naming_convention/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/naming_convention_binary_fail/BUILD.in b/gazelle/python/testdata/naming_convention_binary_fail/BUILD.in
new file mode 100644
index 0000000..fd4dc1c
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_binary_fail/BUILD.in
@@ -0,0 +1 @@
+go_binary(name = "naming_convention_binary_fail_bin")
diff --git a/gazelle/python/testdata/naming_convention_binary_fail/BUILD.out b/gazelle/python/testdata/naming_convention_binary_fail/BUILD.out
new file mode 100644
index 0000000..fd4dc1c
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_binary_fail/BUILD.out
@@ -0,0 +1 @@
+go_binary(name = "naming_convention_binary_fail_bin")
diff --git a/gazelle/python/testdata/naming_convention_binary_fail/README.md b/gazelle/python/testdata/naming_convention_binary_fail/README.md
new file mode 100644
index 0000000..a58bbe4
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_binary_fail/README.md
@@ -0,0 +1,4 @@
+# Naming convention py_binary fail
+
+This test case asserts that a py_binary is not generated due to a naming conflict
+with existing target.
diff --git a/gazelle/python/testdata/naming_convention_binary_fail/WORKSPACE b/gazelle/python/testdata/naming_convention_binary_fail/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_binary_fail/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/naming_convention_binary_fail/__main__.py b/gazelle/python/testdata/naming_convention_binary_fail/__main__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_binary_fail/__main__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/naming_convention_binary_fail/test.yaml b/gazelle/python/testdata/naming_convention_binary_fail/test.yaml
new file mode 100644
index 0000000..41eabbf
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_binary_fail/test.yaml
@@ -0,0 +1,21 @@
+# 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.
+
+---
+expect:
+ exit_code: 1
+ stderr: >
+ gazelle: ERROR: failed to generate target "//:naming_convention_binary_fail_bin" of kind "py_binary":
+ a target of kind "go_binary" with the same name already exists.
+ Use the '# gazelle:python_binary_naming_convention' directive to change the naming convention.
diff --git a/gazelle/python/testdata/naming_convention_library_fail/BUILD.in b/gazelle/python/testdata/naming_convention_library_fail/BUILD.in
new file mode 100644
index 0000000..a684084
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_library_fail/BUILD.in
@@ -0,0 +1 @@
+go_library(name = "naming_convention_library_fail")
diff --git a/gazelle/python/testdata/naming_convention_library_fail/BUILD.out b/gazelle/python/testdata/naming_convention_library_fail/BUILD.out
new file mode 100644
index 0000000..a684084
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_library_fail/BUILD.out
@@ -0,0 +1 @@
+go_library(name = "naming_convention_library_fail")
diff --git a/gazelle/python/testdata/naming_convention_library_fail/README.md b/gazelle/python/testdata/naming_convention_library_fail/README.md
new file mode 100644
index 0000000..cd36917
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_library_fail/README.md
@@ -0,0 +1,4 @@
+# Naming convention py_library fail
+
+This test case asserts that a py_library is not generated due to a naming conflict
+with existing target.
diff --git a/gazelle/python/testdata/naming_convention_library_fail/WORKSPACE b/gazelle/python/testdata/naming_convention_library_fail/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_library_fail/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/naming_convention_library_fail/__init__.py b/gazelle/python/testdata/naming_convention_library_fail/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_library_fail/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/naming_convention_library_fail/test.yaml b/gazelle/python/testdata/naming_convention_library_fail/test.yaml
new file mode 100644
index 0000000..f48aa39
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_library_fail/test.yaml
@@ -0,0 +1,21 @@
+# 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.
+
+---
+expect:
+ exit_code: 1
+ stderr: >
+ gazelle: ERROR: failed to generate target "//:naming_convention_library_fail" of kind "py_library":
+ a target of kind "go_library" with the same name already exists.
+ Use the '# gazelle:python_library_naming_convention' directive to change the naming convention.
diff --git a/gazelle/python/testdata/naming_convention_test_fail/BUILD.in b/gazelle/python/testdata/naming_convention_test_fail/BUILD.in
new file mode 100644
index 0000000..2091253
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_test_fail/BUILD.in
@@ -0,0 +1 @@
+go_test(name = "naming_convention_test_fail_test")
diff --git a/gazelle/python/testdata/naming_convention_test_fail/BUILD.out b/gazelle/python/testdata/naming_convention_test_fail/BUILD.out
new file mode 100644
index 0000000..2091253
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_test_fail/BUILD.out
@@ -0,0 +1 @@
+go_test(name = "naming_convention_test_fail_test")
diff --git a/gazelle/python/testdata/naming_convention_test_fail/README.md b/gazelle/python/testdata/naming_convention_test_fail/README.md
new file mode 100644
index 0000000..886c1e3
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_test_fail/README.md
@@ -0,0 +1,4 @@
+# Naming convention py_test fail
+
+This test case asserts that a py_test is not generated due to a naming conflict
+with existing target.
diff --git a/gazelle/python/testdata/naming_convention_test_fail/WORKSPACE b/gazelle/python/testdata/naming_convention_test_fail/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_test_fail/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/naming_convention_test_fail/__test__.py b/gazelle/python/testdata/naming_convention_test_fail/__test__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_test_fail/__test__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/naming_convention_test_fail/test.yaml b/gazelle/python/testdata/naming_convention_test_fail/test.yaml
new file mode 100644
index 0000000..a8867e5
--- /dev/null
+++ b/gazelle/python/testdata/naming_convention_test_fail/test.yaml
@@ -0,0 +1,21 @@
+# 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.
+
+---
+expect:
+ exit_code: 1
+ stderr: >
+ gazelle: ERROR: failed to generate target "//:naming_convention_test_fail_test" of kind "py_test":
+ a target of kind "go_test" with the same name already exists.
+ Use the '# gazelle:python_test_naming_convention' directive to change the naming convention.
diff --git a/gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.in b/gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.in
new file mode 100644
index 0000000..1ba277a
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.in
@@ -0,0 +1,2 @@
+# gazelle:python_ignore_dependencies foo,bar, baz
+# gazelle:python_ignore_dependencies foo.bar.baz
diff --git a/gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.out b/gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.out
new file mode 100644
index 0000000..3fb91f5
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_dependencies_directive/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_ignore_dependencies foo,bar, baz
+# gazelle:python_ignore_dependencies foo.bar.baz
+
+py_library(
+ name = "python_ignore_dependencies_directive",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test_boto3//:pkg"],
+)
diff --git a/gazelle/python/testdata/python_ignore_dependencies_directive/README.md b/gazelle/python/testdata/python_ignore_dependencies_directive/README.md
new file mode 100644
index 0000000..75f61e1
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_dependencies_directive/README.md
@@ -0,0 +1,4 @@
+# python_ignore_dependencies directive
+
+This test case asserts that the target is generated ignoring some of the
+dependencies.
diff --git a/gazelle/python/testdata/python_ignore_dependencies_directive/WORKSPACE b/gazelle/python/testdata/python_ignore_dependencies_directive/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_dependencies_directive/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/python_ignore_dependencies_directive/__init__.py b/gazelle/python/testdata/python_ignore_dependencies_directive/__init__.py
new file mode 100644
index 0000000..9e6e25a
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_dependencies_directive/__init__.py
@@ -0,0 +1,25 @@
+# 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.
+
+import bar
+import boto3
+import foo
+import foo.bar.baz
+from baz import baz as bazfn
+
+_ = foo
+_ = bar
+_ = bazfn
+_ = baz
+_ = boto3
diff --git a/gazelle/python/testdata/python_ignore_dependencies_directive/gazelle_python.yaml b/gazelle/python/testdata/python_ignore_dependencies_directive/gazelle_python.yaml
new file mode 100644
index 0000000..1bf594f
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_dependencies_directive/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: boto3
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/python_ignore_dependencies_directive/test.yaml b/gazelle/python/testdata/python_ignore_dependencies_directive/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_dependencies_directive/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/python_ignore_files_directive/BUILD.in b/gazelle/python/testdata/python_ignore_files_directive/BUILD.in
new file mode 100644
index 0000000..6277446
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_ignore_files some_other.py
diff --git a/gazelle/python/testdata/python_ignore_files_directive/BUILD.out b/gazelle/python/testdata/python_ignore_files_directive/BUILD.out
new file mode 100644
index 0000000..1fe6030
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/BUILD.out
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_ignore_files some_other.py
+
+py_library(
+ name = "python_ignore_files_directive",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/python_ignore_files_directive/README.md b/gazelle/python/testdata/python_ignore_files_directive/README.md
new file mode 100644
index 0000000..710118d
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/README.md
@@ -0,0 +1,3 @@
+# python_ignore_files directive
+
+This test case asserts that no targets are generated for ignored files.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/WORKSPACE b/gazelle/python/testdata/python_ignore_files_directive/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/__init__.py b/gazelle/python/testdata/python_ignore_files_directive/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.in b/gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.in
diff --git a/gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.out b/gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.out
new file mode 100644
index 0000000..af3c398
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/bar/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "bar",
+ srcs = ["baz.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/python_ignore_files_directive/bar/baz.py b/gazelle/python/testdata/python_ignore_files_directive/bar/baz.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/bar/baz.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/bar/some_other.py b/gazelle/python/testdata/python_ignore_files_directive/bar/some_other.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/bar/some_other.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.in b/gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.in
new file mode 100644
index 0000000..c3049ca
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.in
@@ -0,0 +1 @@
+# gazelle:python_ignore_files baz.py
diff --git a/gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.out b/gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.out
new file mode 100644
index 0000000..c3049ca
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/foo/BUILD.out
@@ -0,0 +1 @@
+# gazelle:python_ignore_files baz.py
diff --git a/gazelle/python/testdata/python_ignore_files_directive/foo/baz.py b/gazelle/python/testdata/python_ignore_files_directive/foo/baz.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/foo/baz.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/setup.py b/gazelle/python/testdata/python_ignore_files_directive/setup.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/setup.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/some_other.py b/gazelle/python/testdata/python_ignore_files_directive/some_other.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/some_other.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/python_ignore_files_directive/test.yaml b/gazelle/python/testdata/python_ignore_files_directive/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/python_ignore_files_directive/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/BUILD.in b/gazelle/python/testdata/python_target_with_test_in_name/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/BUILD.in
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/BUILD.out b/gazelle/python/testdata/python_target_with_test_in_name/BUILD.out
new file mode 100644
index 0000000..a46f5c4
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/BUILD.out
@@ -0,0 +1,22 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "python_target_with_test_in_name",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "real_test",
+ srcs = ["real_test.py"],
+ deps = [
+ ":python_target_with_test_in_name",
+ "@gazelle_python_test_boto3//:pkg",
+ ],
+)
+
+py_test(
+ name = "test_reality",
+ srcs = ["test_reality.py"],
+ deps = [":python_target_with_test_in_name"],
+)
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/README.md b/gazelle/python/testdata/python_target_with_test_in_name/README.md
new file mode 100644
index 0000000..8b592e1
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/README.md
@@ -0,0 +1,3 @@
+# Python target with test in name
+
+Cover the case where a python file either starts with `test_` or ends with `_test`, but is not an actual test.
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/WORKSPACE b/gazelle/python/testdata/python_target_with_test_in_name/WORKSPACE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/WORKSPACE
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/__init__.py b/gazelle/python/testdata/python_target_with_test_in_name/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/gazelle_python.yaml b/gazelle/python/testdata/python_target_with_test_in_name/gazelle_python.yaml
new file mode 100644
index 0000000..1bf594f
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: boto3
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/real_test.py b/gazelle/python/testdata/python_target_with_test_in_name/real_test.py
new file mode 100644
index 0000000..e390866
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/real_test.py
@@ -0,0 +1,18 @@
+# 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.
+
+import boto3
+import __init__
+
+_ = boto3
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/test.yaml b/gazelle/python/testdata/python_target_with_test_in_name/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/python_target_with_test_in_name/test_reality.py b/gazelle/python/testdata/python_target_with_test_in_name/test_reality.py
new file mode 100644
index 0000000..a3afc79
--- /dev/null
+++ b/gazelle/python/testdata/python_target_with_test_in_name/test_reality.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import __init__ \ No newline at end of file
diff --git a/gazelle/python/testdata/relative_imports/BUILD.in b/gazelle/python/testdata/relative_imports/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/BUILD.in
diff --git a/gazelle/python/testdata/relative_imports/BUILD.out b/gazelle/python/testdata/relative_imports/BUILD.out
new file mode 100644
index 0000000..2c08627
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/BUILD.out
@@ -0,0 +1,21 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+
+py_library(
+ name = "relative_imports",
+ srcs = [
+ "package1/module1.py",
+ "package1/module2.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+py_binary(
+ name = "relative_imports_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":relative_imports",
+ "//package2",
+ ],
+)
diff --git a/gazelle/python/testdata/relative_imports/README.md b/gazelle/python/testdata/relative_imports/README.md
new file mode 100644
index 0000000..1937cbc
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/README.md
@@ -0,0 +1,4 @@
+# Relative imports
+
+This test case asserts that the generated targets handle relative imports in
+Python correctly.
diff --git a/gazelle/python/testdata/relative_imports/WORKSPACE b/gazelle/python/testdata/relative_imports/WORKSPACE
new file mode 100644
index 0000000..4959898
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/WORKSPACE
@@ -0,0 +1 @@
+# This is a test data Bazel workspace.
diff --git a/gazelle/python/testdata/relative_imports/__main__.py b/gazelle/python/testdata/relative_imports/__main__.py
new file mode 100644
index 0000000..8d468bd
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/__main__.py
@@ -0,0 +1,19 @@
+# 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.
+
+from package1.module1 import function1
+from package2.module3 import function3
+
+print(function1())
+print(function3())
diff --git a/gazelle/python/testdata/relative_imports/package1/module1.py b/gazelle/python/testdata/relative_imports/package1/module1.py
new file mode 100644
index 0000000..28502f1
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package1/module1.py
@@ -0,0 +1,19 @@
+# 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.
+
+from .module2 import function2
+
+
+def function1():
+ return "function1 " + function2()
diff --git a/gazelle/python/testdata/relative_imports/package1/module2.py b/gazelle/python/testdata/relative_imports/package1/module2.py
new file mode 100644
index 0000000..f8893b2
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package1/module2.py
@@ -0,0 +1,16 @@
+# 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.
+
+def function2():
+ return "function2"
diff --git a/gazelle/python/testdata/relative_imports/package2/BUILD.in b/gazelle/python/testdata/relative_imports/package2/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package2/BUILD.in
diff --git a/gazelle/python/testdata/relative_imports/package2/BUILD.out b/gazelle/python/testdata/relative_imports/package2/BUILD.out
new file mode 100644
index 0000000..bbbc9f8
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package2/BUILD.out
@@ -0,0 +1,13 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "package2",
+ srcs = [
+ "__init__.py",
+ "module3.py",
+ "module4.py",
+ "subpackage1/module5.py",
+ ],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/relative_imports/package2/__init__.py b/gazelle/python/testdata/relative_imports/package2/__init__.py
new file mode 100644
index 0000000..0f59568
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package2/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+class Class1:
+ def method1(self):
+ return "method1"
diff --git a/gazelle/python/testdata/relative_imports/package2/module3.py b/gazelle/python/testdata/relative_imports/package2/module3.py
new file mode 100644
index 0000000..74978a0
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package2/module3.py
@@ -0,0 +1,21 @@
+# 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.
+
+from . import Class1
+from .subpackage1.module5 import function5
+
+
+def function3():
+ c1 = Class1()
+ return "function3 " + c1.method1() + " " + function5()
diff --git a/gazelle/python/testdata/relative_imports/package2/module4.py b/gazelle/python/testdata/relative_imports/package2/module4.py
new file mode 100644
index 0000000..b7509dc
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package2/module4.py
@@ -0,0 +1,16 @@
+# 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.
+
+def function4():
+ return "function4"
diff --git a/gazelle/python/testdata/relative_imports/package2/subpackage1/module5.py b/gazelle/python/testdata/relative_imports/package2/subpackage1/module5.py
new file mode 100644
index 0000000..ea0b981
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/package2/subpackage1/module5.py
@@ -0,0 +1,19 @@
+# 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.
+
+from ..module4 import function4
+
+
+def function5():
+ return "function5 " + function4()
diff --git a/gazelle/python/testdata/relative_imports/test.yaml b/gazelle/python/testdata/relative_imports/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/relative_imports/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/respect_kind_mapping/BUILD.in b/gazelle/python/testdata/respect_kind_mapping/BUILD.in
new file mode 100644
index 0000000..6a06737
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/BUILD.in
@@ -0,0 +1,15 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:map_kind py_test my_test :mytest.bzl
+
+py_library(
+ name = "respect_kind_mapping",
+ srcs = ["__init__.py"],
+)
+
+my_test(
+ name = "respect_kind_mapping_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":respect_kind_mapping"],
+)
diff --git a/gazelle/python/testdata/respect_kind_mapping/BUILD.out b/gazelle/python/testdata/respect_kind_mapping/BUILD.out
new file mode 100644
index 0000000..7c5fb0b
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/BUILD.out
@@ -0,0 +1,20 @@
+load(":mytest.bzl", "my_test")
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:map_kind py_test my_test :mytest.bzl
+
+py_library(
+ name = "respect_kind_mapping",
+ srcs = [
+ "__init__.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+my_test(
+ name = "respect_kind_mapping_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":respect_kind_mapping"],
+)
diff --git a/gazelle/python/testdata/respect_kind_mapping/README.md b/gazelle/python/testdata/respect_kind_mapping/README.md
new file mode 100644
index 0000000..9f0fa6c
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/README.md
@@ -0,0 +1,3 @@
+# Respect Kind Mapping
+
+This test case asserts that when using a kind mapping, gazelle will respect that mapping when parsing a BUILD file containing a mapped kind.
diff --git a/gazelle/python/testdata/respect_kind_mapping/WORKSPACE b/gazelle/python/testdata/respect_kind_mapping/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/respect_kind_mapping/__init__.py b/gazelle/python/testdata/respect_kind_mapping/__init__.py
new file mode 100644
index 0000000..b274b0d
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+from foo import foo
+
+_ = foo
diff --git a/gazelle/python/testdata/respect_kind_mapping/__test__.py b/gazelle/python/testdata/respect_kind_mapping/__test__.py
new file mode 100644
index 0000000..2b180a5
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/__test__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import unittest
+
+from __init__ import foo
+
+
+class FooTest(unittest.TestCase):
+ def test_foo(self):
+ self.assertEqual("foo", foo())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gazelle/python/testdata/respect_kind_mapping/foo.py b/gazelle/python/testdata/respect_kind_mapping/foo.py
new file mode 100644
index 0000000..932de45
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/foo.py
@@ -0,0 +1,16 @@
+# 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.
+
+def foo():
+ return "foo"
diff --git a/gazelle/python/testdata/respect_kind_mapping/test.yaml b/gazelle/python/testdata/respect_kind_mapping/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/respect_kind_mapping/test.yaml
@@ -0,0 +1,17 @@
+# 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.
+
+---
+expect:
+ exit_code: 0
diff --git a/gazelle/python/testdata/sibling_imports/README.md b/gazelle/python/testdata/sibling_imports/README.md
new file mode 100644
index 0000000..e59be07
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/README.md
@@ -0,0 +1,3 @@
+# Sibling imports
+
+This test case asserts that imports from sibling modules are resolved correctly. It covers 3 different types of imports in `pkg/unit_test.py` \ No newline at end of file
diff --git a/gazelle/python/testdata/sibling_imports/WORKSPACE b/gazelle/python/testdata/sibling_imports/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/sibling_imports/pkg/BUILD.in b/gazelle/python/testdata/sibling_imports/pkg/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/BUILD.in
diff --git a/gazelle/python/testdata/sibling_imports/pkg/BUILD.out b/gazelle/python/testdata/sibling_imports/pkg/BUILD.out
new file mode 100644
index 0000000..edb40a8
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/BUILD.out
@@ -0,0 +1,29 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "pkg",
+ srcs = [
+ "__init__.py",
+ "a.py",
+ "b.py",
+ ],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "test_util",
+ srcs = ["test_util.py"],
+ imports = [".."],
+)
+
+py_test(
+ name = "unit_test",
+ srcs = ["unit_test.py"],
+ imports = [".."],
+ deps = [
+ ":pkg",
+ ":test_util",
+ ],
+)
+
diff --git a/gazelle/python/testdata/sibling_imports/pkg/__init__.py b/gazelle/python/testdata/sibling_imports/pkg/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/__init__.py
diff --git a/gazelle/python/testdata/sibling_imports/pkg/a.py b/gazelle/python/testdata/sibling_imports/pkg/a.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/a.py
diff --git a/gazelle/python/testdata/sibling_imports/pkg/b.py b/gazelle/python/testdata/sibling_imports/pkg/b.py
new file mode 100644
index 0000000..7095bdc
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/b.py
@@ -0,0 +1,2 @@
+def run():
+ pass \ No newline at end of file
diff --git a/gazelle/python/testdata/sibling_imports/pkg/test_util.py b/gazelle/python/testdata/sibling_imports/pkg/test_util.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/test_util.py
diff --git a/gazelle/python/testdata/sibling_imports/pkg/unit_test.py b/gazelle/python/testdata/sibling_imports/pkg/unit_test.py
new file mode 100644
index 0000000..a3218e2
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/pkg/unit_test.py
@@ -0,0 +1,3 @@
+import a
+from b import run
+import test_util \ No newline at end of file
diff --git a/gazelle/python/testdata/sibling_imports/test.yaml b/gazelle/python/testdata/sibling_imports/test.yaml
new file mode 100644
index 0000000..ed97d53
--- /dev/null
+++ b/gazelle/python/testdata/sibling_imports/test.yaml
@@ -0,0 +1 @@
+---
diff --git a/gazelle/python/testdata/simple_binary/BUILD.in b/gazelle/python/testdata/simple_binary/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary/BUILD.in
diff --git a/gazelle/python/testdata/simple_binary/BUILD.out b/gazelle/python/testdata/simple_binary/BUILD.out
new file mode 100644
index 0000000..35aa708
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_binary")
+
+py_binary(
+ name = "simple_binary_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/simple_binary/README.md b/gazelle/python/testdata/simple_binary/README.md
new file mode 100644
index 0000000..00c90dc
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary/README.md
@@ -0,0 +1,3 @@
+# Simple binary
+
+This test case asserts that a simple `py_binary` is generated as expected.
diff --git a/gazelle/python/testdata/simple_binary/WORKSPACE b/gazelle/python/testdata/simple_binary/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/simple_binary/__main__.py b/gazelle/python/testdata/simple_binary/__main__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary/__main__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/simple_binary/test.yaml b/gazelle/python/testdata/simple_binary/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/simple_binary_with_library/BUILD.in b/gazelle/python/testdata/simple_binary_with_library/BUILD.in
new file mode 100644
index 0000000..b60e84f
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/BUILD.in
@@ -0,0 +1,18 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "simple_binary_with_library",
+ srcs = [
+ "__init__.py",
+ "bar.py",
+ "foo.py",
+ ],
+)
+
+# This target should be kept unmodified by Gazelle.
+py_library(
+ name = "custom",
+ srcs = [
+ "bar.py",
+ ],
+)
diff --git a/gazelle/python/testdata/simple_binary_with_library/BUILD.out b/gazelle/python/testdata/simple_binary_with_library/BUILD.out
new file mode 100644
index 0000000..eddc15c
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/BUILD.out
@@ -0,0 +1,27 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+
+py_library(
+ name = "simple_binary_with_library",
+ srcs = [
+ "__init__.py",
+ "bar.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+# This target should be kept unmodified by Gazelle.
+py_library(
+ name = "custom",
+ srcs = [
+ "bar.py",
+ ],
+)
+
+py_binary(
+ name = "simple_binary_with_library_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [":simple_binary_with_library"],
+)
diff --git a/gazelle/python/testdata/simple_binary_with_library/README.md b/gazelle/python/testdata/simple_binary_with_library/README.md
new file mode 100644
index 0000000..cfc81a3
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/README.md
@@ -0,0 +1,4 @@
+# Simple binary with library
+
+This test case asserts that a simple `py_binary` is generated as expected
+referencing a `py_library`.
diff --git a/gazelle/python/testdata/simple_binary_with_library/WORKSPACE b/gazelle/python/testdata/simple_binary_with_library/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/simple_binary_with_library/__init__.py b/gazelle/python/testdata/simple_binary_with_library/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/simple_binary_with_library/__main__.py b/gazelle/python/testdata/simple_binary_with_library/__main__.py
new file mode 100644
index 0000000..bc7ddf0
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/__main__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import foo
diff --git a/gazelle/python/testdata/simple_binary_with_library/bar.py b/gazelle/python/testdata/simple_binary_with_library/bar.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/bar.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/simple_binary_with_library/foo.py b/gazelle/python/testdata/simple_binary_with_library/foo.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/foo.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/simple_binary_with_library/test.yaml b/gazelle/python/testdata/simple_binary_with_library/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/simple_binary_with_library/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/simple_library/BUILD.in b/gazelle/python/testdata/simple_library/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/simple_library/BUILD.in
diff --git a/gazelle/python/testdata/simple_library/BUILD.out b/gazelle/python/testdata/simple_library/BUILD.out
new file mode 100644
index 0000000..5793ac2
--- /dev/null
+++ b/gazelle/python/testdata/simple_library/BUILD.out
@@ -0,0 +1,7 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "simple_library",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/simple_library/README.md b/gazelle/python/testdata/simple_library/README.md
new file mode 100644
index 0000000..f88bda1
--- /dev/null
+++ b/gazelle/python/testdata/simple_library/README.md
@@ -0,0 +1,3 @@
+# Simple library
+
+This test case asserts that a simple `py_library` is generated as expected.
diff --git a/gazelle/python/testdata/simple_library/WORKSPACE b/gazelle/python/testdata/simple_library/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/simple_library/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/simple_library/__init__.py b/gazelle/python/testdata/simple_library/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/simple_library/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/simple_library/test.yaml b/gazelle/python/testdata/simple_library/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/simple_library/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/simple_library_without_init/BUILD.in b/gazelle/python/testdata/simple_library_without_init/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/BUILD.in
diff --git a/gazelle/python/testdata/simple_library_without_init/BUILD.out b/gazelle/python/testdata/simple_library_without_init/BUILD.out
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/BUILD.out
diff --git a/gazelle/python/testdata/simple_library_without_init/README.md b/gazelle/python/testdata/simple_library_without_init/README.md
new file mode 100644
index 0000000..5c0a1ca
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/README.md
@@ -0,0 +1,4 @@
+# Simple library without `__init__.py`
+
+This test case asserts that a simple `py_library` is generated as expected
+without an `__init__.py` but with a `BUILD` file marking it as a package.
diff --git a/gazelle/python/testdata/simple_library_without_init/WORKSPACE b/gazelle/python/testdata/simple_library_without_init/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/simple_library_without_init/foo/BUILD.in b/gazelle/python/testdata/simple_library_without_init/foo/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/foo/BUILD.in
diff --git a/gazelle/python/testdata/simple_library_without_init/foo/BUILD.out b/gazelle/python/testdata/simple_library_without_init/foo/BUILD.out
new file mode 100644
index 0000000..2faa046
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/foo/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = ["foo.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/simple_library_without_init/foo/foo.py b/gazelle/python/testdata/simple_library_without_init/foo/foo.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/foo/foo.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/simple_library_without_init/test.yaml b/gazelle/python/testdata/simple_library_without_init/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/simple_library_without_init/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/simple_test/BUILD.in b/gazelle/python/testdata/simple_test/BUILD.in
new file mode 100644
index 0000000..ffd20ea
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/BUILD.in
@@ -0,0 +1,6 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "simple_test",
+ srcs = ["__init__.py"],
+)
diff --git a/gazelle/python/testdata/simple_test/BUILD.out b/gazelle/python/testdata/simple_test/BUILD.out
new file mode 100644
index 0000000..ae2f982
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/BUILD.out
@@ -0,0 +1,17 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "simple_test",
+ srcs = [
+ "__init__.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "simple_test_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [":simple_test"],
+)
diff --git a/gazelle/python/testdata/simple_test/README.md b/gazelle/python/testdata/simple_test/README.md
new file mode 100644
index 0000000..0cfbbeb
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/README.md
@@ -0,0 +1,3 @@
+# Simple test
+
+This test case asserts that a simple `py_test` is generated as expected.
diff --git a/gazelle/python/testdata/simple_test/WORKSPACE b/gazelle/python/testdata/simple_test/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/simple_test/__init__.py b/gazelle/python/testdata/simple_test/__init__.py
new file mode 100644
index 0000000..b274b0d
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+from foo import foo
+
+_ = foo
diff --git a/gazelle/python/testdata/simple_test/__test__.py b/gazelle/python/testdata/simple_test/__test__.py
new file mode 100644
index 0000000..2b180a5
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/__test__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import unittest
+
+from __init__ import foo
+
+
+class FooTest(unittest.TestCase):
+ def test_foo(self):
+ self.assertEqual("foo", foo())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gazelle/python/testdata/simple_test/foo.py b/gazelle/python/testdata/simple_test/foo.py
new file mode 100644
index 0000000..932de45
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/foo.py
@@ -0,0 +1,16 @@
+# 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.
+
+def foo():
+ return "foo"
diff --git a/gazelle/python/testdata/simple_test/test.yaml b/gazelle/python/testdata/simple_test/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/simple_test/test.yaml
@@ -0,0 +1,17 @@
+# 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.
+
+---
+expect:
+ exit_code: 0
diff --git a/gazelle/python/testdata/simple_test_with_conftest/BUILD.in b/gazelle/python/testdata/simple_test_with_conftest/BUILD.in
new file mode 100644
index 0000000..3f2beb3
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/BUILD.in
@@ -0,0 +1 @@
+load("@rules_python//python:defs.bzl", "py_library")
diff --git a/gazelle/python/testdata/simple_test_with_conftest/BUILD.out b/gazelle/python/testdata/simple_test_with_conftest/BUILD.out
new file mode 100644
index 0000000..18079bf
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/BUILD.out
@@ -0,0 +1,27 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "simple_test_with_conftest",
+ srcs = [
+ "__init__.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+py_library(
+ name = "conftest",
+ testonly = True,
+ srcs = ["conftest.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "simple_test_with_conftest_test",
+ srcs = ["__test__.py"],
+ main = "__test__.py",
+ deps = [
+ ":conftest",
+ ":simple_test_with_conftest",
+ ],
+)
diff --git a/gazelle/python/testdata/simple_test_with_conftest/README.md b/gazelle/python/testdata/simple_test_with_conftest/README.md
new file mode 100644
index 0000000..0ff245f
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/README.md
@@ -0,0 +1,4 @@
+# Simple test with conftest.py
+
+This test case asserts that a simple `py_test` is generated as expected when a
+`conftest.py` is present.
diff --git a/gazelle/python/testdata/simple_test_with_conftest/WORKSPACE b/gazelle/python/testdata/simple_test_with_conftest/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/simple_test_with_conftest/__init__.py b/gazelle/python/testdata/simple_test_with_conftest/__init__.py
new file mode 100644
index 0000000..b274b0d
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+from foo import foo
+
+_ = foo
diff --git a/gazelle/python/testdata/simple_test_with_conftest/__test__.py b/gazelle/python/testdata/simple_test_with_conftest/__test__.py
new file mode 100644
index 0000000..2b180a5
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/__test__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import unittest
+
+from __init__ import foo
+
+
+class FooTest(unittest.TestCase):
+ def test_foo(self):
+ self.assertEqual("foo", foo())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.in b/gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.in
new file mode 100644
index 0000000..3f2beb3
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.in
@@ -0,0 +1 @@
+load("@rules_python//python:defs.bzl", "py_library")
diff --git a/gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.out b/gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.out
new file mode 100644
index 0000000..e42c499
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/bar/BUILD.out
@@ -0,0 +1,30 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "bar",
+ srcs = [
+ "__init__.py",
+ "bar.py",
+ ],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_library(
+ name = "conftest",
+ testonly = True,
+ srcs = ["conftest.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "bar_test",
+ srcs = ["__test__.py"],
+ imports = [".."],
+ main = "__test__.py",
+ deps = [
+ ":bar",
+ ":conftest",
+ ],
+)
diff --git a/gazelle/python/testdata/simple_test_with_conftest/bar/__init__.py b/gazelle/python/testdata/simple_test_with_conftest/bar/__init__.py
new file mode 100644
index 0000000..3f0275e
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/bar/__init__.py
@@ -0,0 +1,17 @@
+# 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.
+
+from bar import bar
+
+_ = bar
diff --git a/gazelle/python/testdata/simple_test_with_conftest/bar/__test__.py b/gazelle/python/testdata/simple_test_with_conftest/bar/__test__.py
new file mode 100644
index 0000000..00c4c28
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/bar/__test__.py
@@ -0,0 +1,26 @@
+# 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.
+
+import unittest
+
+from __init__ import bar
+
+
+class BarTest(unittest.TestCase):
+ def test_bar(self):
+ self.assertEqual("bar", bar())
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/gazelle/python/testdata/simple_test_with_conftest/bar/bar.py b/gazelle/python/testdata/simple_test_with_conftest/bar/bar.py
new file mode 100644
index 0000000..ba6a62d
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/bar/bar.py
@@ -0,0 +1,17 @@
+# 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.
+
+
+def bar():
+ return "bar"
diff --git a/gazelle/python/testdata/simple_test_with_conftest/bar/conftest.py b/gazelle/python/testdata/simple_test_with_conftest/bar/conftest.py
new file mode 100644
index 0000000..4101095
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/bar/conftest.py
@@ -0,0 +1,13 @@
+# 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.
diff --git a/gazelle/python/testdata/simple_test_with_conftest/conftest.py b/gazelle/python/testdata/simple_test_with_conftest/conftest.py
new file mode 100644
index 0000000..bbdfb4c
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/conftest.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/gazelle/python/testdata/simple_test_with_conftest/foo.py b/gazelle/python/testdata/simple_test_with_conftest/foo.py
new file mode 100644
index 0000000..932de45
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/foo.py
@@ -0,0 +1,16 @@
+# 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.
+
+def foo():
+ return "foo"
diff --git a/gazelle/python/testdata/simple_test_with_conftest/test.yaml b/gazelle/python/testdata/simple_test_with_conftest/test.yaml
new file mode 100644
index 0000000..2410223
--- /dev/null
+++ b/gazelle/python/testdata/simple_test_with_conftest/test.yaml
@@ -0,0 +1,17 @@
+# 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.
+
+---
+expect:
+ exit_code: 0
diff --git a/gazelle/python/testdata/subdir_sources/BUILD.in b/gazelle/python/testdata/subdir_sources/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/BUILD.out b/gazelle/python/testdata/subdir_sources/BUILD.out
new file mode 100644
index 0000000..d03a8f0
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_binary")
+
+py_binary(
+ name = "subdir_sources_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "//foo",
+ "//one/two",
+ ],
+)
diff --git a/gazelle/python/testdata/subdir_sources/README.md b/gazelle/python/testdata/subdir_sources/README.md
new file mode 100644
index 0000000..79ca3a2
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/README.md
@@ -0,0 +1,5 @@
+# Subdir sources
+
+This test case asserts that `py_library` targets are generated with sources from
+subdirectories and that dependencies are added according to the target that the
+imported source file belongs to.
diff --git a/gazelle/python/testdata/subdir_sources/WORKSPACE b/gazelle/python/testdata/subdir_sources/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/subdir_sources/__main__.py b/gazelle/python/testdata/subdir_sources/__main__.py
new file mode 100644
index 0000000..aacfc67
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/__main__.py
@@ -0,0 +1,21 @@
+# 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.
+
+import foo.bar.bar as bar
+import foo.baz.baz as baz
+import one.two.three as three
+
+_ = bar
+_ = baz
+_ = three
diff --git a/gazelle/python/testdata/subdir_sources/foo/BUILD.in b/gazelle/python/testdata/subdir_sources/foo/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/foo/BUILD.out b/gazelle/python/testdata/subdir_sources/foo/BUILD.out
new file mode 100644
index 0000000..f99857d
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/BUILD.out
@@ -0,0 +1,13 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "foo",
+ srcs = [
+ "__init__.py",
+ "bar/bar.py",
+ "baz/baz.py",
+ "foo.py",
+ ],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/subdir_sources/foo/__init__.py b/gazelle/python/testdata/subdir_sources/foo/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/bar/bar.py b/gazelle/python/testdata/subdir_sources/foo/bar/bar.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/bar/bar.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/baz/baz.py b/gazelle/python/testdata/subdir_sources/foo/baz/baz.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/baz/baz.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/foo.py b/gazelle/python/testdata/subdir_sources/foo/foo.py
new file mode 100644
index 0000000..a98c73d
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/foo.py
@@ -0,0 +1,17 @@
+# 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.
+
+import foo.bar.bar as bar
+
+_ = bar
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.in b/gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.out b/gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.out
new file mode 100644
index 0000000..0ef0cc1
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_build/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "has_build",
+ srcs = ["python/my_module.py"],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_build/python/my_module.py b/gazelle/python/testdata/subdir_sources/foo/has_build/python/my_module.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_build/python/my_module.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_build_bazel/BUILD.bazel.in b/gazelle/python/testdata/subdir_sources/foo/has_build_bazel/BUILD.bazel.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_build_bazel/BUILD.bazel.in
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_build_bazel/python/my_module.py b/gazelle/python/testdata/subdir_sources/foo/has_build_bazel/python/my_module.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_build_bazel/python/my_module.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.in b/gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.out b/gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.out
new file mode 100644
index 0000000..ce59ee2
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_init/BUILD.out
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "has_init",
+ srcs = [
+ "__init__.py",
+ "python/my_module.py",
+ ],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_init/__init__.py b/gazelle/python/testdata/subdir_sources/foo/has_init/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_init/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_init/python/my_module.py b/gazelle/python/testdata/subdir_sources/foo/has_init/python/my_module.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_init/python/my_module.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.in b/gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.out b/gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.out
new file mode 100644
index 0000000..265c08b
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_main/BUILD.out
@@ -0,0 +1,17 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+
+py_library(
+ name = "has_main",
+ srcs = ["python/my_module.py"],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_binary(
+ name = "has_main_bin",
+ srcs = ["__main__.py"],
+ imports = ["../.."],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [":has_main"],
+)
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py b/gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py
new file mode 100644
index 0000000..bd0fe61
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_main/__main__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import foo.has_main.python.my_module \ No newline at end of file
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_main/python/my_module.py b/gazelle/python/testdata/subdir_sources/foo/has_main/python/my_module.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_main/python/my_module.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.in b/gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.out b/gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.out
new file mode 100644
index 0000000..80739d9
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_test/BUILD.out
@@ -0,0 +1,16 @@
+load("@rules_python//python:defs.bzl", "py_library", "py_test")
+
+py_library(
+ name = "has_test",
+ srcs = ["python/my_module.py"],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+)
+
+py_test(
+ name = "has_test_test",
+ srcs = ["__test__.py"],
+ imports = ["../.."],
+ main = "__test__.py",
+ deps = [":has_test"],
+)
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py b/gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py
new file mode 100644
index 0000000..3c9ed1a
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_test/__test__.py
@@ -0,0 +1,16 @@
+# 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.
+
+# For test purposes only.
+import foo.has_test.python.my_module \ No newline at end of file
diff --git a/gazelle/python/testdata/subdir_sources/foo/has_test/python/my_module.py b/gazelle/python/testdata/subdir_sources/foo/has_test/python/my_module.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/foo/has_test/python/my_module.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/one/BUILD.in b/gazelle/python/testdata/subdir_sources/one/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/one/BUILD.out b/gazelle/python/testdata/subdir_sources/one/BUILD.out
new file mode 100644
index 0000000..f2e5745
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "one",
+ srcs = ["__init__.py"],
+ imports = [".."],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/subdir_sources/one/__init__.py b/gazelle/python/testdata/subdir_sources/one/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/one/two/BUILD.in b/gazelle/python/testdata/subdir_sources/one/two/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/two/BUILD.in
diff --git a/gazelle/python/testdata/subdir_sources/one/two/BUILD.out b/gazelle/python/testdata/subdir_sources/one/two/BUILD.out
new file mode 100644
index 0000000..f632eed
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/two/BUILD.out
@@ -0,0 +1,12 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "two",
+ srcs = [
+ "__init__.py",
+ "three.py",
+ ],
+ imports = ["../.."],
+ visibility = ["//:__subpackages__"],
+ deps = ["//foo"],
+)
diff --git a/gazelle/python/testdata/subdir_sources/one/two/README.md b/gazelle/python/testdata/subdir_sources/one/two/README.md
new file mode 100644
index 0000000..ec4c15d
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/two/README.md
@@ -0,0 +1,2 @@
+# Same package imports
+This test case asserts that no `deps` is needed when a module imports another module in the same package \ No newline at end of file
diff --git a/gazelle/python/testdata/subdir_sources/one/two/__init__.py b/gazelle/python/testdata/subdir_sources/one/two/__init__.py
new file mode 100644
index 0000000..72357b3
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/two/__init__.py
@@ -0,0 +1,18 @@
+# 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.
+
+import foo.baz.baz as baz
+import three
+
+_ = baz
diff --git a/gazelle/python/testdata/subdir_sources/one/two/three.py b/gazelle/python/testdata/subdir_sources/one/two/three.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/one/two/three.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/subdir_sources/test.yaml b/gazelle/python/testdata/subdir_sources/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/subdir_sources/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/with_nested_import_statements/BUILD.in b/gazelle/python/testdata/with_nested_import_statements/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/with_nested_import_statements/BUILD.in
diff --git a/gazelle/python/testdata/with_nested_import_statements/BUILD.out b/gazelle/python/testdata/with_nested_import_statements/BUILD.out
new file mode 100644
index 0000000..45bf265
--- /dev/null
+++ b/gazelle/python/testdata/with_nested_import_statements/BUILD.out
@@ -0,0 +1,8 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "with_nested_import_statements",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test_boto3//:pkg"],
+)
diff --git a/gazelle/python/testdata/with_nested_import_statements/README.md b/gazelle/python/testdata/with_nested_import_statements/README.md
new file mode 100644
index 0000000..7213b34
--- /dev/null
+++ b/gazelle/python/testdata/with_nested_import_statements/README.md
@@ -0,0 +1,4 @@
+# With nested import statements
+
+This test case asserts that a `py_library` is generated with dependencies
+extracted from nested import statements from the Python source file.
diff --git a/gazelle/python/testdata/with_nested_import_statements/WORKSPACE b/gazelle/python/testdata/with_nested_import_statements/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/with_nested_import_statements/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/with_nested_import_statements/__init__.py b/gazelle/python/testdata/with_nested_import_statements/__init__.py
new file mode 100644
index 0000000..733b51f
--- /dev/null
+++ b/gazelle/python/testdata/with_nested_import_statements/__init__.py
@@ -0,0 +1,25 @@
+# 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.
+
+import os
+import sys
+
+_ = os
+_ = sys
+
+
+def main():
+ import boto3
+
+ _ = boto3
diff --git a/gazelle/python/testdata/with_nested_import_statements/gazelle_python.yaml b/gazelle/python/testdata/with_nested_import_statements/gazelle_python.yaml
new file mode 100644
index 0000000..1bf594f
--- /dev/null
+++ b/gazelle/python/testdata/with_nested_import_statements/gazelle_python.yaml
@@ -0,0 +1,18 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: boto3
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/with_nested_import_statements/test.yaml b/gazelle/python/testdata/with_nested_import_statements/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/with_nested_import_statements/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/with_std_requirements/BUILD.in b/gazelle/python/testdata/with_std_requirements/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/with_std_requirements/BUILD.in
diff --git a/gazelle/python/testdata/with_std_requirements/BUILD.out b/gazelle/python/testdata/with_std_requirements/BUILD.out
new file mode 100644
index 0000000..a382ca8
--- /dev/null
+++ b/gazelle/python/testdata/with_std_requirements/BUILD.out
@@ -0,0 +1,7 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "with_std_requirements",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/with_std_requirements/README.md b/gazelle/python/testdata/with_std_requirements/README.md
new file mode 100644
index 0000000..4eaf1b0
--- /dev/null
+++ b/gazelle/python/testdata/with_std_requirements/README.md
@@ -0,0 +1,4 @@
+# With std requirements
+
+This test case asserts that a `py_library` is generated without any `deps` since
+it only imports Python standard library packages.
diff --git a/gazelle/python/testdata/with_std_requirements/WORKSPACE b/gazelle/python/testdata/with_std_requirements/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/with_std_requirements/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/with_std_requirements/__init__.py b/gazelle/python/testdata/with_std_requirements/__init__.py
new file mode 100644
index 0000000..e51d320
--- /dev/null
+++ b/gazelle/python/testdata/with_std_requirements/__init__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import os
+import sys
+
+_ = os
+_ = sys
diff --git a/gazelle/python/testdata/with_std_requirements/test.yaml b/gazelle/python/testdata/with_std_requirements/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/with_std_requirements/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/with_third_party_requirements/BUILD.in b/gazelle/python/testdata/with_third_party_requirements/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/BUILD.in
diff --git a/gazelle/python/testdata/with_third_party_requirements/BUILD.out b/gazelle/python/testdata/with_third_party_requirements/BUILD.out
new file mode 100644
index 0000000..2a97d8b
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/BUILD.out
@@ -0,0 +1,24 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+
+py_library(
+ name = "with_third_party_requirements",
+ srcs = [
+ "__init__.py",
+ "bar.py",
+ "foo.py",
+ ],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "@gazelle_python_test_baz//:pkg",
+ "@gazelle_python_test_boto3//:pkg",
+ "@gazelle_python_test_djangorestframework//:pkg",
+ ],
+)
+
+py_binary(
+ name = "with_third_party_requirements_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = ["@gazelle_python_test_baz//:pkg"],
+)
diff --git a/gazelle/python/testdata/with_third_party_requirements/README.md b/gazelle/python/testdata/with_third_party_requirements/README.md
new file mode 100644
index 0000000..a7ef7a3
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/README.md
@@ -0,0 +1,7 @@
+# With third-party requirements
+
+This test case asserts that
+* a `py_library` is generated with dependencies
+extracted from its sources and a `py_binary` is generated embeding the
+`py_library` and inherits its dependencies, without specifying the `deps` again.
+* when a third-party library and a module in the same package having the same name, the one in the same package takes precedence.
diff --git a/gazelle/python/testdata/with_third_party_requirements/WORKSPACE b/gazelle/python/testdata/with_third_party_requirements/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/with_third_party_requirements/__init__.py b/gazelle/python/testdata/with_third_party_requirements/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/with_third_party_requirements/__main__.py b/gazelle/python/testdata/with_third_party_requirements/__main__.py
new file mode 100644
index 0000000..38e9a55
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/__main__.py
@@ -0,0 +1,19 @@
+# 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.
+
+import bar
+import foo
+
+_ = bar
+_ = foo
diff --git a/gazelle/python/testdata/with_third_party_requirements/bar.py b/gazelle/python/testdata/with_third_party_requirements/bar.py
new file mode 100644
index 0000000..08f2e7c
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/bar.py
@@ -0,0 +1,25 @@
+# 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.
+
+import os
+
+import bar
+import boto3
+import rest_framework
+
+_ = os
+
+_ = bar
+_ = boto3
+_ = rest_framework
diff --git a/gazelle/python/testdata/with_third_party_requirements/foo.py b/gazelle/python/testdata/with_third_party_requirements/foo.py
new file mode 100644
index 0000000..9bebbfc
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/foo.py
@@ -0,0 +1,25 @@
+# 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.
+
+import sys
+
+import boto3
+import foo
+import rest_framework
+
+_ = sys
+
+_ = boto3
+_ = foo
+_ = rest_framework
diff --git a/gazelle/python/testdata/with_third_party_requirements/gazelle_python.yaml b/gazelle/python/testdata/with_third_party_requirements/gazelle_python.yaml
new file mode 100644
index 0000000..7753cff
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/gazelle_python.yaml
@@ -0,0 +1,21 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ boto3: boto3
+ rest_framework: djangorestframework
+ foo: baz
+ bar: baz
+ pip_deps_repository_name: gazelle_python_test
diff --git a/gazelle/python/testdata/with_third_party_requirements/test.yaml b/gazelle/python/testdata/with_third_party_requirements/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.in b/gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.in
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.in
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.out b/gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.out
new file mode 100644
index 0000000..577f167
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/BUILD.out
@@ -0,0 +1,25 @@
+load("@rules_python//python:defs.bzl", "py_binary", "py_library")
+
+py_library(
+ name = "with_third_party_requirements_from_imports",
+ srcs = [
+ "__init__.py",
+ "bar.py",
+ ],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ "@gazelle_python_test_google_cloud_aiplatform//:pkg",
+ "@gazelle_python_test_google_cloud_storage//:pkg",
+ ],
+)
+
+py_binary(
+ name = "with_third_party_requirements_from_imports_bin",
+ srcs = ["__main__.py"],
+ main = "__main__.py",
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":with_third_party_requirements_from_imports",
+ "@gazelle_python_test_google_cloud_aiplatform//:pkg",
+ ],
+)
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/README.md b/gazelle/python/testdata/with_third_party_requirements_from_imports/README.md
new file mode 100644
index 0000000..c50a1ca
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/README.md
@@ -0,0 +1,15 @@
+# With third-party requirements (from imports)
+
+This test case covers imports of the form:
+
+```python
+from my_pip_dep import foo
+```
+
+for example
+
+```python
+from google.cloud import aiplatform, storage
+```
+
+See https://github.com/bazelbuild/rules_python/issues/709 and https://github.com/sramirezmartin/gazelle-toy-example.
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/WORKSPACE b/gazelle/python/testdata/with_third_party_requirements_from_imports/WORKSPACE
new file mode 100644
index 0000000..faff6af
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/__init__.py b/gazelle/python/testdata/with_third_party_requirements_from_imports/__init__.py
new file mode 100644
index 0000000..7307559
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+# For test purposes only.
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/__main__.py b/gazelle/python/testdata/with_third_party_requirements_from_imports/__main__.py
new file mode 100644
index 0000000..2062a9b
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/__main__.py
@@ -0,0 +1,20 @@
+# 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.
+
+from bar import main
+from google.cloud import aiplatform
+
+if __name__ == "__main__":
+ print(aiplatform)
+ main()
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/bar.py b/gazelle/python/testdata/with_third_party_requirements_from_imports/bar.py
new file mode 100644
index 0000000..6886b2b
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/bar.py
@@ -0,0 +1,20 @@
+# 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.
+
+from google.cloud import aiplatform, storage
+
+
+def main():
+ a = dir(aiplatform)
+ b = dir(storage)
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/gazelle_python.yaml b/gazelle/python/testdata/with_third_party_requirements_from_imports/gazelle_python.yaml
new file mode 100644
index 0000000..8b5694b
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/gazelle_python.yaml
@@ -0,0 +1,1678 @@
+# 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.
+
+manifest:
+ modules_mapping:
+ cachetools: cachetools
+ cachetools.__init__: cachetools
+ cachetools.func: cachetools
+ cachetools.keys: cachetools
+ certifi: certifi
+ certifi.__init__: certifi
+ certifi.__main__: certifi
+ certifi.core: certifi
+ charset_normalizer: charset_normalizer
+ charset_normalizer.__init__: charset_normalizer
+ charset_normalizer.api: charset_normalizer
+ charset_normalizer.assets: charset_normalizer
+ charset_normalizer.assets.__init__: charset_normalizer
+ charset_normalizer.cd: charset_normalizer
+ charset_normalizer.cli: charset_normalizer
+ charset_normalizer.cli.__init__: charset_normalizer
+ charset_normalizer.cli.normalizer: charset_normalizer
+ charset_normalizer.constant: charset_normalizer
+ charset_normalizer.legacy: charset_normalizer
+ charset_normalizer.md: charset_normalizer
+ charset_normalizer.models: charset_normalizer
+ charset_normalizer.utils: charset_normalizer
+ charset_normalizer.version: charset_normalizer
+ dateutil: python_dateutil
+ dateutil.__init__: python_dateutil
+ dateutil._common: python_dateutil
+ dateutil._version: python_dateutil
+ dateutil.easter: python_dateutil
+ dateutil.parser: python_dateutil
+ dateutil.parser.__init__: python_dateutil
+ dateutil.parser._parser: python_dateutil
+ dateutil.parser.isoparser: python_dateutil
+ dateutil.relativedelta: python_dateutil
+ dateutil.rrule: python_dateutil
+ dateutil.tz: python_dateutil
+ dateutil.tz.__init__: python_dateutil
+ dateutil.tz._common: python_dateutil
+ dateutil.tz._factories: python_dateutil
+ dateutil.tz.tz: python_dateutil
+ dateutil.tz.win: python_dateutil
+ dateutil.tzwin: python_dateutil
+ dateutil.utils: python_dateutil
+ dateutil.zoneinfo: python_dateutil
+ dateutil.zoneinfo.__init__: python_dateutil
+ dateutil.zoneinfo.rebuild: python_dateutil
+ docs.conf: google_cloud_resource_manager
+ google._async_resumable_media: google_resumable_media
+ google._async_resumable_media.__init__: google_resumable_media
+ google._async_resumable_media._download: google_resumable_media
+ google._async_resumable_media._helpers: google_resumable_media
+ google._async_resumable_media._upload: google_resumable_media
+ google._async_resumable_media.requests: google_resumable_media
+ google._async_resumable_media.requests.__init__: google_resumable_media
+ google._async_resumable_media.requests._request_helpers: google_resumable_media
+ google._async_resumable_media.requests.download: google_resumable_media
+ google._async_resumable_media.requests.upload: google_resumable_media
+ google.api: googleapis_common_protos
+ google.api.__init__: googleapis_common_protos
+ google.api.annotations_pb2: googleapis_common_protos
+ google.api.auth_pb2: googleapis_common_protos
+ google.api.backend_pb2: googleapis_common_protos
+ google.api.billing_pb2: googleapis_common_protos
+ google.api.client_pb2: googleapis_common_protos
+ google.api.config_change_pb2: googleapis_common_protos
+ google.api.consumer_pb2: googleapis_common_protos
+ google.api.context_pb2: googleapis_common_protos
+ google.api.control_pb2: googleapis_common_protos
+ google.api.distribution_pb2: googleapis_common_protos
+ google.api.documentation_pb2: googleapis_common_protos
+ google.api.endpoint_pb2: googleapis_common_protos
+ google.api.error_reason_pb2: googleapis_common_protos
+ google.api.field_behavior_pb2: googleapis_common_protos
+ google.api.http_pb2: googleapis_common_protos
+ google.api.httpbody_pb2: googleapis_common_protos
+ google.api.label_pb2: googleapis_common_protos
+ google.api.launch_stage_pb2: googleapis_common_protos
+ google.api.log_pb2: googleapis_common_protos
+ google.api.logging_pb2: googleapis_common_protos
+ google.api.metric_pb2: googleapis_common_protos
+ google.api.monitored_resource_pb2: googleapis_common_protos
+ google.api.monitoring_pb2: googleapis_common_protos
+ google.api.quota_pb2: googleapis_common_protos
+ google.api.resource_pb2: googleapis_common_protos
+ google.api.routing_pb2: googleapis_common_protos
+ google.api.service_pb2: googleapis_common_protos
+ google.api.source_info_pb2: googleapis_common_protos
+ google.api.system_parameter_pb2: googleapis_common_protos
+ google.api.usage_pb2: googleapis_common_protos
+ google.api.visibility_pb2: googleapis_common_protos
+ google.api_core: google_api_core
+ google.api_core.__init__: google_api_core
+ google.api_core.bidi: google_api_core
+ google.api_core.client_info: google_api_core
+ google.api_core.client_options: google_api_core
+ google.api_core.datetime_helpers: google_api_core
+ google.api_core.exceptions: google_api_core
+ google.api_core.extended_operation: google_api_core
+ google.api_core.future: google_api_core
+ google.api_core.future.__init__: google_api_core
+ google.api_core.future._helpers: google_api_core
+ google.api_core.future.async_future: google_api_core
+ google.api_core.future.base: google_api_core
+ google.api_core.future.polling: google_api_core
+ google.api_core.gapic_v1: google_api_core
+ google.api_core.gapic_v1.__init__: google_api_core
+ google.api_core.gapic_v1.client_info: google_api_core
+ google.api_core.gapic_v1.config: google_api_core
+ google.api_core.gapic_v1.config_async: google_api_core
+ google.api_core.gapic_v1.method: google_api_core
+ google.api_core.gapic_v1.method_async: google_api_core
+ google.api_core.gapic_v1.routing_header: google_api_core
+ google.api_core.general_helpers: google_api_core
+ google.api_core.grpc_helpers: google_api_core
+ google.api_core.grpc_helpers_async: google_api_core
+ google.api_core.iam: google_api_core
+ google.api_core.operation: google_api_core
+ google.api_core.operation_async: google_api_core
+ google.api_core.operations_v1: google_api_core
+ google.api_core.operations_v1.__init__: google_api_core
+ google.api_core.operations_v1.abstract_operations_client: google_api_core
+ google.api_core.operations_v1.operations_async_client: google_api_core
+ google.api_core.operations_v1.operations_client: google_api_core
+ google.api_core.operations_v1.operations_client_config: google_api_core
+ google.api_core.operations_v1.pagers: google_api_core
+ google.api_core.operations_v1.transports: google_api_core
+ google.api_core.operations_v1.transports.__init__: google_api_core
+ google.api_core.operations_v1.transports.base: google_api_core
+ google.api_core.operations_v1.transports.rest: google_api_core
+ google.api_core.page_iterator: google_api_core
+ google.api_core.page_iterator_async: google_api_core
+ google.api_core.path_template: google_api_core
+ google.api_core.protobuf_helpers: google_api_core
+ google.api_core.rest_helpers: google_api_core
+ google.api_core.rest_streaming: google_api_core
+ google.api_core.retry: google_api_core
+ google.api_core.retry_async: google_api_core
+ google.api_core.timeout: google_api_core
+ google.api_core.version: google_api_core
+ google.auth: google_auth
+ google.auth.__init__: google_auth
+ google.auth._cloud_sdk: google_auth
+ google.auth._credentials_async: google_auth
+ google.auth._default: google_auth
+ google.auth._default_async: google_auth
+ google.auth._helpers: google_auth
+ google.auth._jwt_async: google_auth
+ google.auth._oauth2client: google_auth
+ google.auth._service_account_info: google_auth
+ google.auth.app_engine: google_auth
+ google.auth.aws: google_auth
+ google.auth.compute_engine: google_auth
+ google.auth.compute_engine.__init__: google_auth
+ google.auth.compute_engine._metadata: google_auth
+ google.auth.compute_engine.credentials: google_auth
+ google.auth.credentials: google_auth
+ google.auth.crypt: google_auth
+ google.auth.crypt.__init__: google_auth
+ google.auth.crypt._cryptography_rsa: google_auth
+ google.auth.crypt._helpers: google_auth
+ google.auth.crypt._python_rsa: google_auth
+ google.auth.crypt.base: google_auth
+ google.auth.crypt.es256: google_auth
+ google.auth.crypt.rsa: google_auth
+ google.auth.downscoped: google_auth
+ google.auth.environment_vars: google_auth
+ google.auth.exceptions: google_auth
+ google.auth.external_account: google_auth
+ google.auth.iam: google_auth
+ google.auth.identity_pool: google_auth
+ google.auth.impersonated_credentials: google_auth
+ google.auth.jwt: google_auth
+ google.auth.transport: google_auth
+ google.auth.transport.__init__: google_auth
+ google.auth.transport._aiohttp_requests: google_auth
+ google.auth.transport._http_client: google_auth
+ google.auth.transport._mtls_helper: google_auth
+ google.auth.transport.grpc: google_auth
+ google.auth.transport.mtls: google_auth
+ google.auth.transport.requests: google_auth
+ google.auth.transport.urllib3: google_auth
+ google.auth.version: google_auth
+ google.cloud._helpers: google_cloud_core
+ google.cloud._helpers.__init__: google_cloud_core
+ google.cloud._http: google_cloud_core
+ google.cloud._http.__init__: google_cloud_core
+ google.cloud._testing: google_cloud_core
+ google.cloud._testing.__init__: google_cloud_core
+ google.cloud.aiplatform: google_cloud_aiplatform
+ google.cloud.aiplatform.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform._matching_engine: google_cloud_aiplatform
+ google.cloud.aiplatform._matching_engine.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform._matching_engine.match_service_pb2: google_cloud_aiplatform
+ google.cloud.aiplatform._matching_engine.match_service_pb2_grpc: google_cloud_aiplatform
+ google.cloud.aiplatform._matching_engine.matching_engine_index: google_cloud_aiplatform
+ google.cloud.aiplatform._matching_engine.matching_engine_index_config: google_cloud_aiplatform
+ google.cloud.aiplatform._matching_engine.matching_engine_index_endpoint: google_cloud_aiplatform
+ google.cloud.aiplatform.base: google_cloud_aiplatform
+ google.cloud.aiplatform.compat: google_cloud_aiplatform
+ google.cloud.aiplatform.compat.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.compat.services: google_cloud_aiplatform
+ google.cloud.aiplatform.compat.services.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.compat.types: google_cloud_aiplatform
+ google.cloud.aiplatform.compat.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.constants: google_cloud_aiplatform
+ google.cloud.aiplatform.constants.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.constants.base: google_cloud_aiplatform
+ google.cloud.aiplatform.constants.prediction: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets._datasources: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.column_names_dataset: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.dataset: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.image_dataset: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.tabular_dataset: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.text_dataset: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.time_series_dataset: google_cloud_aiplatform
+ google.cloud.aiplatform.datasets.video_dataset: google_cloud_aiplatform
+ google.cloud.aiplatform.explain: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.lit: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.metadata_builder: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf.v1: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf.v1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf.v1.saved_model_metadata_builder: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf.v2: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf.v2.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.explain.metadata.tf.v2.saved_model_metadata_builder: google_cloud_aiplatform
+ google.cloud.aiplatform.featurestore: google_cloud_aiplatform
+ google.cloud.aiplatform.featurestore.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.featurestore.entity_type: google_cloud_aiplatform
+ google.cloud.aiplatform.featurestore.feature: google_cloud_aiplatform
+ google.cloud.aiplatform.featurestore.featurestore: google_cloud_aiplatform
+ google.cloud.aiplatform.gapic: google_cloud_aiplatform
+ google.cloud.aiplatform.gapic.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.gapic.schema: google_cloud_aiplatform
+ google.cloud.aiplatform.gapic.schema.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.helpers: google_cloud_aiplatform
+ google.cloud.aiplatform.helpers.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.helpers.container_uri_builders: google_cloud_aiplatform
+ google.cloud.aiplatform.hyperparameter_tuning: google_cloud_aiplatform
+ google.cloud.aiplatform.initializer: google_cloud_aiplatform
+ google.cloud.aiplatform.jobs: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.artifact: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.constants: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.context: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.execution: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.metadata: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.metadata_store: google_cloud_aiplatform
+ google.cloud.aiplatform.metadata.resource: google_cloud_aiplatform
+ google.cloud.aiplatform.model_evaluation: google_cloud_aiplatform
+ google.cloud.aiplatform.model_evaluation.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.model_evaluation.model_evaluation: google_cloud_aiplatform
+ google.cloud.aiplatform.models: google_cloud_aiplatform
+ google.cloud.aiplatform.pipeline_jobs: google_cloud_aiplatform
+ google.cloud.aiplatform.schema: google_cloud_aiplatform
+ google.cloud.aiplatform.tensorboard: google_cloud_aiplatform
+ google.cloud.aiplatform.tensorboard.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.tensorboard.plugins.tf_profiler.profile_uploader: google_cloud_aiplatform
+ google.cloud.aiplatform.tensorboard.tensorboard_resource: google_cloud_aiplatform
+ google.cloud.aiplatform.tensorboard.uploader: google_cloud_aiplatform
+ google.cloud.aiplatform.tensorboard.uploader_main: google_cloud_aiplatform
+ google.cloud.aiplatform.tensorboard.uploader_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.training_jobs: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.cloud_profiler_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.initializer: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.plugins.base_plugin: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.plugins.tensorflow.tensorboard_api: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.plugins.tensorflow.tf_profiler: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.webserver: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.cloud_profiler.wsgi_types: google_cloud_aiplatform
+ google.cloud.aiplatform.training_utils.environment_variables: google_cloud_aiplatform
+ google.cloud.aiplatform.utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.column_transformations_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.console_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.enhanced_library: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.enhanced_library.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.enhanced_library._decorators: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.enhanced_library.value_converter: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.featurestore_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.gcs_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.pipeline_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.resource_manager_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.source_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.tensorboard_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.worker_spec_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.utils.yaml_utils: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.image_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.text_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.text_extraction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.text_sentiment: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.instance_v1.types.video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types.image_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types.image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types.image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types.video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types.video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.params_v1.types.video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.tabular_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.tabular_regression: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.text_extraction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.text_sentiment: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.predict.prediction_v1.types.video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_image_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_tables: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_text_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_text_extraction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_text_sentiment: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.automl_video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1.schema.trainingjob.definition_v1.types.export_evaluated_data_items_config: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.image_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.text_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.text_extraction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.text_sentiment: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.instance_v1beta1.types.video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types.image_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types.image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types.image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types.video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types.video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.params_v1beta1.types.video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.tabular_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.tabular_regression: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.text_extraction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.text_sentiment: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.time_series_forecasting: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.predict.prediction_v1beta1.types.video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_forecasting: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_image_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_image_object_detection: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_image_segmentation: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_tables: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_text_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_text_extraction: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_text_sentiment: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_time_series_forecasting: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_video_action_recognition: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_video_classification: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.automl_video_object_tracking: google_cloud_aiplatform
+ google.cloud.aiplatform.v1beta1.schema.trainingjob.definition_v1beta1.types.export_evaluated_data_items_config: google_cloud_aiplatform
+ google.cloud.aiplatform.version: google_cloud_aiplatform
+ google.cloud.aiplatform_v1: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.dataset_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.endpoint_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_online_serving_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.featurestore_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_endpoint_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.index_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.job_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.metadata_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.migration_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.model_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.pipeline_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.prediction_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.specialist_pool_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.tensorboard_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.services.vizier_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.accelerator_type: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.annotation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.annotation_spec: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.artifact: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.batch_prediction_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.completion_stats: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.context: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.custom_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.data_item: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.data_labeling_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.dataset: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.dataset_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.deployed_index_ref: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.deployed_model_ref: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.encryption_spec: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.endpoint: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.entity_type: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.env_var: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.event: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.execution: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.explanation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.explanation_metadata: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.feature: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.feature_monitoring_stats: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.feature_selector: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.featurestore: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.featurestore_monitoring: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.featurestore_online_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.featurestore_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.hyperparameter_tuning_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.index: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.index_endpoint: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.index_endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.index_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.io: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.job_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.job_state: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.lineage_subgraph: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.machine_resources: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.manual_batch_tuning_parameters: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.metadata_schema: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.metadata_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.metadata_store: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.migratable_resource: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.migration_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.model: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.model_deployment_monitoring_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.model_evaluation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.model_evaluation_slice: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.model_monitoring: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.model_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.operation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.pipeline_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.pipeline_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.pipeline_state: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.prediction_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.specialist_pool: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.specialist_pool_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.study: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.tensorboard: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.tensorboard_data: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.tensorboard_experiment: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.tensorboard_run: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.tensorboard_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.tensorboard_time_series: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.training_pipeline: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.types: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.unmanaged_container_model: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.user_action_reference: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.value: google_cloud_aiplatform
+ google.cloud.aiplatform_v1.types.vizier_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.dataset_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.endpoint_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_online_serving_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.featurestore_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_endpoint_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.index_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.job_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.metadata_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.migration_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.model_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.pipeline_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.prediction_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.specialist_pool_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.tensorboard_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.async_client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.client: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.pagers: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.transports: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.transports.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.transports.base: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.transports.grpc: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.services.vizier_service.transports.grpc_asyncio: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.__init__: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.accelerator_type: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.annotation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.annotation_spec: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.artifact: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.batch_prediction_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.completion_stats: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.context: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.custom_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.data_item: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.data_labeling_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.dataset: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.dataset_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.deployed_index_ref: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.deployed_model_ref: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.encryption_spec: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.endpoint: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.entity_type: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.env_var: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.event: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.execution: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.explanation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.explanation_metadata: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.feature: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.feature_monitoring_stats: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.feature_selector: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.featurestore: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.featurestore_monitoring: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.featurestore_online_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.featurestore_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.hyperparameter_tuning_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.index: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.index_endpoint: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.index_endpoint_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.index_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.io: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.job_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.job_state: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.lineage_subgraph: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.machine_resources: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.manual_batch_tuning_parameters: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.metadata_schema: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.metadata_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.metadata_store: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.migratable_resource: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.migration_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.model: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.model_deployment_monitoring_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.model_evaluation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.model_evaluation_slice: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.model_monitoring: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.model_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.operation: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.pipeline_job: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.pipeline_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.pipeline_state: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.prediction_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.specialist_pool: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.specialist_pool_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.study: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.tensorboard: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.tensorboard_data: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.tensorboard_experiment: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.tensorboard_run: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.tensorboard_service: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.tensorboard_time_series: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.training_pipeline: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.types: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.unmanaged_container_model: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.user_action_reference: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.value: google_cloud_aiplatform
+ google.cloud.aiplatform_v1beta1.types.vizier_service: google_cloud_aiplatform
+ google.cloud.bigquery: google_cloud_bigquery
+ google.cloud.bigquery.__init__: google_cloud_bigquery
+ google.cloud.bigquery._helpers: google_cloud_bigquery
+ google.cloud.bigquery._http: google_cloud_bigquery
+ google.cloud.bigquery._pandas_helpers: google_cloud_bigquery
+ google.cloud.bigquery._tqdm_helpers: google_cloud_bigquery
+ google.cloud.bigquery.client: google_cloud_bigquery
+ google.cloud.bigquery.dataset: google_cloud_bigquery
+ google.cloud.bigquery.dbapi: google_cloud_bigquery
+ google.cloud.bigquery.dbapi.__init__: google_cloud_bigquery
+ google.cloud.bigquery.dbapi._helpers: google_cloud_bigquery
+ google.cloud.bigquery.dbapi.connection: google_cloud_bigquery
+ google.cloud.bigquery.dbapi.cursor: google_cloud_bigquery
+ google.cloud.bigquery.dbapi.exceptions: google_cloud_bigquery
+ google.cloud.bigquery.dbapi.types: google_cloud_bigquery
+ google.cloud.bigquery.encryption_configuration: google_cloud_bigquery
+ google.cloud.bigquery.enums: google_cloud_bigquery
+ google.cloud.bigquery.exceptions: google_cloud_bigquery
+ google.cloud.bigquery.external_config: google_cloud_bigquery
+ google.cloud.bigquery.format_options: google_cloud_bigquery
+ google.cloud.bigquery.iam: google_cloud_bigquery
+ google.cloud.bigquery.job: google_cloud_bigquery
+ google.cloud.bigquery.job.__init__: google_cloud_bigquery
+ google.cloud.bigquery.job.base: google_cloud_bigquery
+ google.cloud.bigquery.job.copy_: google_cloud_bigquery
+ google.cloud.bigquery.job.extract: google_cloud_bigquery
+ google.cloud.bigquery.job.load: google_cloud_bigquery
+ google.cloud.bigquery.job.query: google_cloud_bigquery
+ google.cloud.bigquery.magics: google_cloud_bigquery
+ google.cloud.bigquery.magics.__init__: google_cloud_bigquery
+ google.cloud.bigquery.magics.line_arg_parser: google_cloud_bigquery
+ google.cloud.bigquery.magics.line_arg_parser.__init__: google_cloud_bigquery
+ google.cloud.bigquery.magics.line_arg_parser.exceptions: google_cloud_bigquery
+ google.cloud.bigquery.magics.line_arg_parser.lexer: google_cloud_bigquery
+ google.cloud.bigquery.magics.line_arg_parser.parser: google_cloud_bigquery
+ google.cloud.bigquery.magics.line_arg_parser.visitors: google_cloud_bigquery
+ google.cloud.bigquery.magics.magics: google_cloud_bigquery
+ google.cloud.bigquery.model: google_cloud_bigquery
+ google.cloud.bigquery.opentelemetry_tracing: google_cloud_bigquery
+ google.cloud.bigquery.query: google_cloud_bigquery
+ google.cloud.bigquery.retry: google_cloud_bigquery
+ google.cloud.bigquery.routine: google_cloud_bigquery
+ google.cloud.bigquery.routine.__init__: google_cloud_bigquery
+ google.cloud.bigquery.routine.routine: google_cloud_bigquery
+ google.cloud.bigquery.schema: google_cloud_bigquery
+ google.cloud.bigquery.table: google_cloud_bigquery
+ google.cloud.bigquery.version: google_cloud_bigquery
+ google.cloud.bigquery_v2: google_cloud_bigquery
+ google.cloud.bigquery_v2.__init__: google_cloud_bigquery
+ google.cloud.bigquery_v2.types: google_cloud_bigquery
+ google.cloud.bigquery_v2.types.__init__: google_cloud_bigquery
+ google.cloud.bigquery_v2.types.encryption_config: google_cloud_bigquery
+ google.cloud.bigquery_v2.types.model: google_cloud_bigquery
+ google.cloud.bigquery_v2.types.model_reference: google_cloud_bigquery
+ google.cloud.bigquery_v2.types.standard_sql: google_cloud_bigquery
+ google.cloud.bigquery_v2.types.table_reference: google_cloud_bigquery
+ google.cloud.client: google_cloud_core
+ google.cloud.client.__init__: google_cloud_core
+ google.cloud.environment_vars: google_cloud_core
+ google.cloud.environment_vars.__init__: google_cloud_core
+ google.cloud.exceptions: google_cloud_core
+ google.cloud.exceptions.__init__: google_cloud_core
+ google.cloud.extended_operations_pb2: googleapis_common_protos
+ google.cloud.location.locations_pb2: googleapis_common_protos
+ google.cloud.obsolete: google_cloud_core
+ google.cloud.obsolete.__init__: google_cloud_core
+ google.cloud.operation: google_cloud_core
+ google.cloud.operation.__init__: google_cloud_core
+ google.cloud.resourcemanager: google_cloud_resource_manager
+ google.cloud.resourcemanager.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.async_client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.pagers: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.transports: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.transports.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.transports.base: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.transports.grpc: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.folders.transports.grpc_asyncio: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.async_client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.pagers: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.transports: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.transports.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.transports.base: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.transports.grpc: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.organizations.transports.grpc_asyncio: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.async_client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.pagers: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.transports: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.transports.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.transports.base: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.transports.grpc: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.projects.transports.grpc_asyncio: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.async_client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.pagers: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.transports: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.transports.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.transports.base: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.transports.grpc: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_bindings.transports.grpc_asyncio: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.async_client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.pagers: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.transports: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.transports.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.transports.base: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.transports.grpc: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_keys.transports.grpc_asyncio: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.async_client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.client: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.pagers: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.transports: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.transports.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.transports.base: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.transports.grpc: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.services.tag_values.transports.grpc_asyncio: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types.__init__: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types.folders: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types.organizations: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types.projects: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types.tag_bindings: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types.tag_keys: google_cloud_resource_manager
+ google.cloud.resourcemanager_v3.types.tag_values: google_cloud_resource_manager
+ google.cloud.storage: google_cloud_storage
+ google.cloud.storage.__init__: google_cloud_storage
+ google.cloud.storage._helpers: google_cloud_storage
+ google.cloud.storage._http: google_cloud_storage
+ google.cloud.storage._signing: google_cloud_storage
+ google.cloud.storage.acl: google_cloud_storage
+ google.cloud.storage.batch: google_cloud_storage
+ google.cloud.storage.blob: google_cloud_storage
+ google.cloud.storage.bucket: google_cloud_storage
+ google.cloud.storage.client: google_cloud_storage
+ google.cloud.storage.constants: google_cloud_storage
+ google.cloud.storage.fileio: google_cloud_storage
+ google.cloud.storage.hmac_key: google_cloud_storage
+ google.cloud.storage.iam: google_cloud_storage
+ google.cloud.storage.notification: google_cloud_storage
+ google.cloud.storage.retry: google_cloud_storage
+ google.cloud.storage.version: google_cloud_storage
+ google.cloud.version: google_cloud_core
+ google.gapic.metadata: googleapis_common_protos
+ google.gapic.metadata.__init__: googleapis_common_protos
+ google.gapic.metadata.gapic_metadata_pb2: googleapis_common_protos
+ google.iam.v1: grpc_google_iam_v1
+ google.iam.v1.__init__: grpc_google_iam_v1
+ google.iam.v1.iam_policy_pb2: grpc_google_iam_v1
+ google.iam.v1.iam_policy_pb2_grpc: grpc_google_iam_v1
+ google.iam.v1.logging: grpc_google_iam_v1
+ google.iam.v1.logging.__init__: grpc_google_iam_v1
+ google.iam.v1.logging.audit_data_pb2: grpc_google_iam_v1
+ google.iam.v1.options_pb2: grpc_google_iam_v1
+ google.iam.v1.options_pb2_grpc: grpc_google_iam_v1
+ google.iam.v1.policy_pb2: grpc_google_iam_v1
+ google.iam.v1.policy_pb2_grpc: grpc_google_iam_v1
+ google.logging.type: googleapis_common_protos
+ google.logging.type.__init__: googleapis_common_protos
+ google.logging.type.http_request_pb2: googleapis_common_protos
+ google.logging.type.log_severity_pb2: googleapis_common_protos
+ google.longrunning: googleapis_common_protos
+ google.longrunning.__init__: googleapis_common_protos
+ google.longrunning.operations_grpc: googleapis_common_protos
+ google.longrunning.operations_grpc_pb2: googleapis_common_protos
+ google.longrunning.operations_pb2: googleapis_common_protos
+ google.longrunning.operations_pb2_grpc: googleapis_common_protos
+ google.longrunning.operations_proto: googleapis_common_protos
+ google.longrunning.operations_proto_pb2: googleapis_common_protos
+ google.oauth2: google_auth
+ google.oauth2.__init__: google_auth
+ google.oauth2._client: google_auth
+ google.oauth2._client_async: google_auth
+ google.oauth2._credentials_async: google_auth
+ google.oauth2._id_token_async: google_auth
+ google.oauth2._reauth_async: google_auth
+ google.oauth2._service_account_async: google_auth
+ google.oauth2.challenges: google_auth
+ google.oauth2.credentials: google_auth
+ google.oauth2.id_token: google_auth
+ google.oauth2.reauth: google_auth
+ google.oauth2.service_account: google_auth
+ google.oauth2.sts: google_auth
+ google.oauth2.utils: google_auth
+ google.protobuf: protobuf
+ google.protobuf.__init__: protobuf
+ google.protobuf.any_pb2: protobuf
+ google.protobuf.api_pb2: protobuf
+ google.protobuf.compiler: protobuf
+ google.protobuf.compiler.__init__: protobuf
+ google.protobuf.compiler.plugin_pb2: protobuf
+ google.protobuf.descriptor: protobuf
+ google.protobuf.descriptor_database: protobuf
+ google.protobuf.descriptor_pb2: protobuf
+ google.protobuf.descriptor_pool: protobuf
+ google.protobuf.duration_pb2: protobuf
+ google.protobuf.empty_pb2: protobuf
+ google.protobuf.field_mask_pb2: protobuf
+ google.protobuf.internal: protobuf
+ google.protobuf.internal.__init__: protobuf
+ google.protobuf.internal._api_implementation: protobuf
+ google.protobuf.internal.api_implementation: protobuf
+ google.protobuf.internal.builder: protobuf
+ google.protobuf.internal.containers: protobuf
+ google.protobuf.internal.decoder: protobuf
+ google.protobuf.internal.encoder: protobuf
+ google.protobuf.internal.enum_type_wrapper: protobuf
+ google.protobuf.internal.extension_dict: protobuf
+ google.protobuf.internal.message_listener: protobuf
+ google.protobuf.internal.python_message: protobuf
+ google.protobuf.internal.type_checkers: protobuf
+ google.protobuf.internal.well_known_types: protobuf
+ google.protobuf.internal.wire_format: protobuf
+ google.protobuf.json_format: protobuf
+ google.protobuf.message: protobuf
+ google.protobuf.message_factory: protobuf
+ google.protobuf.proto_builder: protobuf
+ google.protobuf.pyext: protobuf
+ google.protobuf.pyext.__init__: protobuf
+ google.protobuf.pyext._message: protobuf
+ google.protobuf.pyext.cpp_message: protobuf
+ google.protobuf.reflection: protobuf
+ google.protobuf.service: protobuf
+ google.protobuf.service_reflection: protobuf
+ google.protobuf.source_context_pb2: protobuf
+ google.protobuf.struct_pb2: protobuf
+ google.protobuf.symbol_database: protobuf
+ google.protobuf.text_encoding: protobuf
+ google.protobuf.text_format: protobuf
+ google.protobuf.timestamp_pb2: protobuf
+ google.protobuf.type_pb2: protobuf
+ google.protobuf.util: protobuf
+ google.protobuf.util.__init__: protobuf
+ google.protobuf.util.json_format_pb2: protobuf
+ google.protobuf.util.json_format_proto3_pb2: protobuf
+ google.protobuf.wrappers_pb2: protobuf
+ google.resumable_media: google_resumable_media
+ google.resumable_media.__init__: google_resumable_media
+ google.resumable_media._download: google_resumable_media
+ google.resumable_media._helpers: google_resumable_media
+ google.resumable_media._upload: google_resumable_media
+ google.resumable_media.common: google_resumable_media
+ google.resumable_media.requests: google_resumable_media
+ google.resumable_media.requests.__init__: google_resumable_media
+ google.resumable_media.requests._request_helpers: google_resumable_media
+ google.resumable_media.requests.download: google_resumable_media
+ google.resumable_media.requests.upload: google_resumable_media
+ google.rpc: googleapis_common_protos
+ google.rpc.__init__: googleapis_common_protos
+ google.rpc.code_pb2: googleapis_common_protos
+ google.rpc.context: googleapis_common_protos
+ google.rpc.context.__init__: googleapis_common_protos
+ google.rpc.context.attribute_context_pb2: googleapis_common_protos
+ google.rpc.error_details_pb2: googleapis_common_protos
+ google.rpc.status_pb2: googleapis_common_protos
+ google.type: googleapis_common_protos
+ google.type.__init__: googleapis_common_protos
+ google.type.calendar_period_pb2: googleapis_common_protos
+ google.type.color_pb2: googleapis_common_protos
+ google.type.date_pb2: googleapis_common_protos
+ google.type.datetime_pb2: googleapis_common_protos
+ google.type.dayofweek_pb2: googleapis_common_protos
+ google.type.decimal_pb2: googleapis_common_protos
+ google.type.expr_pb2: googleapis_common_protos
+ google.type.fraction_pb2: googleapis_common_protos
+ google.type.interval_pb2: googleapis_common_protos
+ google.type.latlng_pb2: googleapis_common_protos
+ google.type.localized_text_pb2: googleapis_common_protos
+ google.type.money_pb2: googleapis_common_protos
+ google.type.month_pb2: googleapis_common_protos
+ google.type.phone_number_pb2: googleapis_common_protos
+ google.type.postal_address_pb2: googleapis_common_protos
+ google.type.quaternion_pb2: googleapis_common_protos
+ google.type.timeofday_pb2: googleapis_common_protos
+ google_crc32c: google_crc32c
+ google_crc32c.__config__: google_crc32c
+ google_crc32c.__init__: google_crc32c
+ google_crc32c._checksum: google_crc32c
+ google_crc32c._crc32c: google_crc32c
+ google_crc32c.cext: google_crc32c
+ google_crc32c.libs.libcrc32c-672e1704: google_crc32c
+ google_crc32c.python: google_crc32c
+ grpc: grpcio
+ grpc.__init__: grpcio
+ grpc._auth: grpcio
+ grpc._channel: grpcio
+ grpc._common: grpcio
+ grpc._compression: grpcio
+ grpc._cython: grpcio
+ grpc._cython.__init__: grpcio
+ grpc._cython._cygrpc: grpcio
+ grpc._cython._cygrpc.__init__: grpcio
+ grpc._cython.cygrpc: grpcio
+ grpc._grpcio_metadata: grpcio
+ grpc._interceptor: grpcio
+ grpc._plugin_wrapping: grpcio
+ grpc._runtime_protos: grpcio
+ grpc._server: grpcio
+ grpc._simple_stubs: grpcio
+ grpc._utilities: grpcio
+ grpc.aio: grpcio
+ grpc.aio.__init__: grpcio
+ grpc.aio._base_call: grpcio
+ grpc.aio._base_channel: grpcio
+ grpc.aio._base_server: grpcio
+ grpc.aio._call: grpcio
+ grpc.aio._channel: grpcio
+ grpc.aio._interceptor: grpcio
+ grpc.aio._metadata: grpcio
+ grpc.aio._server: grpcio
+ grpc.aio._typing: grpcio
+ grpc.aio._utils: grpcio
+ grpc.beta: grpcio
+ grpc.beta.__init__: grpcio
+ grpc.beta._client_adaptations: grpcio
+ grpc.beta._metadata: grpcio
+ grpc.beta._server_adaptations: grpcio
+ grpc.beta.implementations: grpcio
+ grpc.beta.interfaces: grpcio
+ grpc.beta.utilities: grpcio
+ grpc.experimental: grpcio
+ grpc.experimental.__init__: grpcio
+ grpc.experimental.aio: grpcio
+ grpc.experimental.aio.__init__: grpcio
+ grpc.experimental.gevent: grpcio
+ grpc.experimental.session_cache: grpcio
+ grpc.framework: grpcio
+ grpc.framework.__init__: grpcio
+ grpc.framework.common: grpcio
+ grpc.framework.common.__init__: grpcio
+ grpc.framework.common.cardinality: grpcio
+ grpc.framework.common.style: grpcio
+ grpc.framework.foundation: grpcio
+ grpc.framework.foundation.__init__: grpcio
+ grpc.framework.foundation.abandonment: grpcio
+ grpc.framework.foundation.callable_util: grpcio
+ grpc.framework.foundation.future: grpcio
+ grpc.framework.foundation.logging_pool: grpcio
+ grpc.framework.foundation.stream: grpcio
+ grpc.framework.foundation.stream_util: grpcio
+ grpc.framework.interfaces: grpcio
+ grpc.framework.interfaces.__init__: grpcio
+ grpc.framework.interfaces.base: grpcio
+ grpc.framework.interfaces.base.__init__: grpcio
+ grpc.framework.interfaces.base.base: grpcio
+ grpc.framework.interfaces.base.utilities: grpcio
+ grpc.framework.interfaces.face: grpcio
+ grpc.framework.interfaces.face.__init__: grpcio
+ grpc.framework.interfaces.face.face: grpcio
+ grpc.framework.interfaces.face.utilities: grpcio
+ grpc_status: grpcio_status
+ grpc_status.__init__: grpcio_status
+ grpc_status._async: grpcio_status
+ grpc_status._common: grpcio_status
+ grpc_status.rpc_status: grpcio_status
+ idna: idna
+ idna.__init__: idna
+ idna.codec: idna
+ idna.compat: idna
+ idna.core: idna
+ idna.idnadata: idna
+ idna.intranges: idna
+ idna.package_data: idna
+ idna.uts46data: idna
+ packaging: packaging
+ packaging.__about__: packaging
+ packaging.__init__: packaging
+ packaging._manylinux: packaging
+ packaging._musllinux: packaging
+ packaging._structures: packaging
+ packaging.markers: packaging
+ packaging.requirements: packaging
+ packaging.specifiers: packaging
+ packaging.tags: packaging
+ packaging.utils: packaging
+ packaging.version: packaging
+ proto: proto_plus
+ proto.__init__: proto_plus
+ proto._file_info: proto_plus
+ proto._package_info: proto_plus
+ proto.datetime_helpers: proto_plus
+ proto.enums: proto_plus
+ proto.fields: proto_plus
+ proto.marshal: proto_plus
+ proto.marshal.__init__: proto_plus
+ proto.marshal.collections: proto_plus
+ proto.marshal.collections.__init__: proto_plus
+ proto.marshal.collections.maps: proto_plus
+ proto.marshal.collections.repeated: proto_plus
+ proto.marshal.compat: proto_plus
+ proto.marshal.marshal: proto_plus
+ proto.marshal.rules: proto_plus
+ proto.marshal.rules.__init__: proto_plus
+ proto.marshal.rules.bytes: proto_plus
+ proto.marshal.rules.dates: proto_plus
+ proto.marshal.rules.enums: proto_plus
+ proto.marshal.rules.message: proto_plus
+ proto.marshal.rules.stringy_numbers: proto_plus
+ proto.marshal.rules.struct: proto_plus
+ proto.marshal.rules.wrappers: proto_plus
+ proto.message: proto_plus
+ proto.modules: proto_plus
+ proto.primitives: proto_plus
+ proto.utils: proto_plus
+ pyasn1: pyasn1
+ pyasn1.__init__: pyasn1
+ pyasn1.codec: pyasn1
+ pyasn1.codec.__init__: pyasn1
+ pyasn1.codec.ber: pyasn1
+ pyasn1.codec.ber.__init__: pyasn1
+ pyasn1.codec.ber.decoder: pyasn1
+ pyasn1.codec.ber.encoder: pyasn1
+ pyasn1.codec.ber.eoo: pyasn1
+ pyasn1.codec.cer: pyasn1
+ pyasn1.codec.cer.__init__: pyasn1
+ pyasn1.codec.cer.decoder: pyasn1
+ pyasn1.codec.cer.encoder: pyasn1
+ pyasn1.codec.der: pyasn1
+ pyasn1.codec.der.__init__: pyasn1
+ pyasn1.codec.der.decoder: pyasn1
+ pyasn1.codec.der.encoder: pyasn1
+ pyasn1.codec.native: pyasn1
+ pyasn1.codec.native.__init__: pyasn1
+ pyasn1.codec.native.decoder: pyasn1
+ pyasn1.codec.native.encoder: pyasn1
+ pyasn1.compat: pyasn1
+ pyasn1.compat.__init__: pyasn1
+ pyasn1.compat.binary: pyasn1
+ pyasn1.compat.calling: pyasn1
+ pyasn1.compat.dateandtime: pyasn1
+ pyasn1.compat.integer: pyasn1
+ pyasn1.compat.octets: pyasn1
+ pyasn1.compat.string: pyasn1
+ pyasn1.debug: pyasn1
+ pyasn1.error: pyasn1
+ pyasn1.type: pyasn1
+ pyasn1.type.__init__: pyasn1
+ pyasn1.type.base: pyasn1
+ pyasn1.type.char: pyasn1
+ pyasn1.type.constraint: pyasn1
+ pyasn1.type.error: pyasn1
+ pyasn1.type.namedtype: pyasn1
+ pyasn1.type.namedval: pyasn1
+ pyasn1.type.opentype: pyasn1
+ pyasn1.type.tag: pyasn1
+ pyasn1.type.tagmap: pyasn1
+ pyasn1.type.univ: pyasn1
+ pyasn1.type.useful: pyasn1
+ pyasn1_modules: pyasn1_modules
+ pyasn1_modules.__init__: pyasn1_modules
+ pyasn1_modules.pem: pyasn1_modules
+ pyasn1_modules.rfc1155: pyasn1_modules
+ pyasn1_modules.rfc1157: pyasn1_modules
+ pyasn1_modules.rfc1901: pyasn1_modules
+ pyasn1_modules.rfc1902: pyasn1_modules
+ pyasn1_modules.rfc1905: pyasn1_modules
+ pyasn1_modules.rfc2251: pyasn1_modules
+ pyasn1_modules.rfc2314: pyasn1_modules
+ pyasn1_modules.rfc2315: pyasn1_modules
+ pyasn1_modules.rfc2437: pyasn1_modules
+ pyasn1_modules.rfc2459: pyasn1_modules
+ pyasn1_modules.rfc2511: pyasn1_modules
+ pyasn1_modules.rfc2560: pyasn1_modules
+ pyasn1_modules.rfc2631: pyasn1_modules
+ pyasn1_modules.rfc2634: pyasn1_modules
+ pyasn1_modules.rfc2985: pyasn1_modules
+ pyasn1_modules.rfc2986: pyasn1_modules
+ pyasn1_modules.rfc3114: pyasn1_modules
+ pyasn1_modules.rfc3161: pyasn1_modules
+ pyasn1_modules.rfc3274: pyasn1_modules
+ pyasn1_modules.rfc3279: pyasn1_modules
+ pyasn1_modules.rfc3280: pyasn1_modules
+ pyasn1_modules.rfc3281: pyasn1_modules
+ pyasn1_modules.rfc3412: pyasn1_modules
+ pyasn1_modules.rfc3414: pyasn1_modules
+ pyasn1_modules.rfc3447: pyasn1_modules
+ pyasn1_modules.rfc3560: pyasn1_modules
+ pyasn1_modules.rfc3565: pyasn1_modules
+ pyasn1_modules.rfc3709: pyasn1_modules
+ pyasn1_modules.rfc3770: pyasn1_modules
+ pyasn1_modules.rfc3779: pyasn1_modules
+ pyasn1_modules.rfc3852: pyasn1_modules
+ pyasn1_modules.rfc4043: pyasn1_modules
+ pyasn1_modules.rfc4055: pyasn1_modules
+ pyasn1_modules.rfc4073: pyasn1_modules
+ pyasn1_modules.rfc4108: pyasn1_modules
+ pyasn1_modules.rfc4210: pyasn1_modules
+ pyasn1_modules.rfc4211: pyasn1_modules
+ pyasn1_modules.rfc4334: pyasn1_modules
+ pyasn1_modules.rfc4985: pyasn1_modules
+ pyasn1_modules.rfc5035: pyasn1_modules
+ pyasn1_modules.rfc5083: pyasn1_modules
+ pyasn1_modules.rfc5084: pyasn1_modules
+ pyasn1_modules.rfc5208: pyasn1_modules
+ pyasn1_modules.rfc5280: pyasn1_modules
+ pyasn1_modules.rfc5480: pyasn1_modules
+ pyasn1_modules.rfc5649: pyasn1_modules
+ pyasn1_modules.rfc5652: pyasn1_modules
+ pyasn1_modules.rfc5751: pyasn1_modules
+ pyasn1_modules.rfc5755: pyasn1_modules
+ pyasn1_modules.rfc5913: pyasn1_modules
+ pyasn1_modules.rfc5914: pyasn1_modules
+ pyasn1_modules.rfc5915: pyasn1_modules
+ pyasn1_modules.rfc5916: pyasn1_modules
+ pyasn1_modules.rfc5917: pyasn1_modules
+ pyasn1_modules.rfc5924: pyasn1_modules
+ pyasn1_modules.rfc5934: pyasn1_modules
+ pyasn1_modules.rfc5940: pyasn1_modules
+ pyasn1_modules.rfc5958: pyasn1_modules
+ pyasn1_modules.rfc5990: pyasn1_modules
+ pyasn1_modules.rfc6010: pyasn1_modules
+ pyasn1_modules.rfc6019: pyasn1_modules
+ pyasn1_modules.rfc6031: pyasn1_modules
+ pyasn1_modules.rfc6032: pyasn1_modules
+ pyasn1_modules.rfc6120: pyasn1_modules
+ pyasn1_modules.rfc6170: pyasn1_modules
+ pyasn1_modules.rfc6187: pyasn1_modules
+ pyasn1_modules.rfc6210: pyasn1_modules
+ pyasn1_modules.rfc6211: pyasn1_modules
+ pyasn1_modules.rfc6402: pyasn1_modules
+ pyasn1_modules.rfc6402-1: pyasn1_modules
+ pyasn1_modules.rfc6482: pyasn1_modules
+ pyasn1_modules.rfc6486: pyasn1_modules
+ pyasn1_modules.rfc6487: pyasn1_modules
+ pyasn1_modules.rfc6664: pyasn1_modules
+ pyasn1_modules.rfc6955: pyasn1_modules
+ pyasn1_modules.rfc6960: pyasn1_modules
+ pyasn1_modules.rfc7030: pyasn1_modules
+ pyasn1_modules.rfc7191: pyasn1_modules
+ pyasn1_modules.rfc7229: pyasn1_modules
+ pyasn1_modules.rfc7292: pyasn1_modules
+ pyasn1_modules.rfc7296: pyasn1_modules
+ pyasn1_modules.rfc7508: pyasn1_modules
+ pyasn1_modules.rfc7585: pyasn1_modules
+ pyasn1_modules.rfc7633: pyasn1_modules
+ pyasn1_modules.rfc7773: pyasn1_modules
+ pyasn1_modules.rfc7894: pyasn1_modules
+ pyasn1_modules.rfc7894-1: pyasn1_modules
+ pyasn1_modules.rfc7906: pyasn1_modules
+ pyasn1_modules.rfc7914: pyasn1_modules
+ pyasn1_modules.rfc8017: pyasn1_modules
+ pyasn1_modules.rfc8018: pyasn1_modules
+ pyasn1_modules.rfc8103: pyasn1_modules
+ pyasn1_modules.rfc8209: pyasn1_modules
+ pyasn1_modules.rfc8226: pyasn1_modules
+ pyasn1_modules.rfc8358: pyasn1_modules
+ pyasn1_modules.rfc8360: pyasn1_modules
+ pyasn1_modules.rfc8398: pyasn1_modules
+ pyasn1_modules.rfc8410: pyasn1_modules
+ pyasn1_modules.rfc8418: pyasn1_modules
+ pyasn1_modules.rfc8419: pyasn1_modules
+ pyasn1_modules.rfc8479: pyasn1_modules
+ pyasn1_modules.rfc8494: pyasn1_modules
+ pyasn1_modules.rfc8520: pyasn1_modules
+ pyasn1_modules.rfc8619: pyasn1_modules
+ pyasn1_modules.rfc8649: pyasn1_modules
+ pyparsing: pyparsing
+ pyparsing.__init__: pyparsing
+ pyparsing.actions: pyparsing
+ pyparsing.common: pyparsing
+ pyparsing.core: pyparsing
+ pyparsing.diagram: pyparsing
+ pyparsing.diagram.__init__: pyparsing
+ pyparsing.exceptions: pyparsing
+ pyparsing.helpers: pyparsing
+ pyparsing.results: pyparsing
+ pyparsing.testing: pyparsing
+ pyparsing.unicode: pyparsing
+ pyparsing.util: pyparsing
+ requests: requests
+ requests.__init__: requests
+ requests.__version__: requests
+ requests._internal_utils: requests
+ requests.adapters: requests
+ requests.api: requests
+ requests.auth: requests
+ requests.certs: requests
+ requests.compat: requests
+ requests.cookies: requests
+ requests.exceptions: requests
+ requests.help: requests
+ requests.hooks: requests
+ requests.models: requests
+ requests.packages: requests
+ requests.sessions: requests
+ requests.status_codes: requests
+ requests.structures: requests
+ requests.utils: requests
+ rsa: rsa
+ rsa.__init__: rsa
+ rsa._compat: rsa
+ rsa.asn1: rsa
+ rsa.cli: rsa
+ rsa.common: rsa
+ rsa.core: rsa
+ rsa.key: rsa
+ rsa.parallel: rsa
+ rsa.pem: rsa
+ rsa.pkcs1: rsa
+ rsa.pkcs1_v2: rsa
+ rsa.prime: rsa
+ rsa.randnum: rsa
+ rsa.transform: rsa
+ rsa.util: rsa
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_create_folder_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_create_folder_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_delete_folder_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_delete_folder_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_get_folder_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_get_folder_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_get_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_get_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_list_folders_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_list_folders_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_move_folder_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_move_folder_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_search_folders_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_search_folders_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_set_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_set_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_test_iam_permissions_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_test_iam_permissions_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_undelete_folder_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_undelete_folder_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_update_folder_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_folders_update_folder_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_get_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_get_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_get_organization_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_get_organization_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_search_organizations_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_search_organizations_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_set_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_set_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_test_iam_permissions_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_organizations_test_iam_permissions_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_create_project_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_create_project_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_delete_project_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_delete_project_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_get_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_get_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_get_project_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_get_project_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_list_projects_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_list_projects_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_move_project_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_move_project_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_search_projects_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_search_projects_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_set_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_set_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_test_iam_permissions_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_test_iam_permissions_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_undelete_project_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_undelete_project_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_update_project_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_projects_update_project_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_bindings_create_tag_binding_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_bindings_create_tag_binding_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_bindings_delete_tag_binding_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_bindings_delete_tag_binding_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_bindings_list_tag_bindings_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_bindings_list_tag_bindings_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_create_tag_key_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_create_tag_key_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_delete_tag_key_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_delete_tag_key_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_get_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_get_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_get_tag_key_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_get_tag_key_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_list_tag_keys_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_list_tag_keys_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_set_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_set_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_test_iam_permissions_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_test_iam_permissions_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_update_tag_key_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_keys_update_tag_key_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_create_tag_value_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_create_tag_value_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_delete_tag_value_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_delete_tag_value_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_get_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_get_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_get_tag_value_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_get_tag_value_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_list_tag_values_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_list_tag_values_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_set_iam_policy_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_set_iam_policy_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_test_iam_permissions_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_test_iam_permissions_sync: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_update_tag_value_async: google_cloud_resource_manager
+ samples.generated_samples.cloudresourcemanager_v3_generated_tag_values_update_tag_value_sync: google_cloud_resource_manager
+ scripts.fixup_resourcemanager_v3_keywords: google_cloud_resource_manager
+ scripts.readme-gen.readme_gen: google_cloud_resource_manager
+ six: six
+ tests: google_cloud_resource_manager
+ tests.__init__: google_cloud_resource_manager
+ tests.unit: google_cloud_resource_manager
+ tests.unit.__init__: google_cloud_resource_manager
+ tests.unit.gapic: google_cloud_resource_manager
+ tests.unit.gapic.__init__: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3.__init__: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3.test_folders: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3.test_organizations: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3.test_projects: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3.test_tag_bindings: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3.test_tag_keys: google_cloud_resource_manager
+ tests.unit.gapic.resourcemanager_v3.test_tag_values: google_cloud_resource_manager
+ urllib3: urllib3
+ urllib3.__init__: urllib3
+ urllib3._collections: urllib3
+ urllib3._version: urllib3
+ urllib3.connection: urllib3
+ urllib3.connectionpool: urllib3
+ urllib3.contrib: urllib3
+ urllib3.contrib.__init__: urllib3
+ urllib3.contrib._appengine_environ: urllib3
+ urllib3.contrib._securetransport: urllib3
+ urllib3.contrib._securetransport.__init__: urllib3
+ urllib3.contrib._securetransport.bindings: urllib3
+ urllib3.contrib._securetransport.low_level: urllib3
+ urllib3.contrib.appengine: urllib3
+ urllib3.contrib.ntlmpool: urllib3
+ urllib3.contrib.pyopenssl: urllib3
+ urllib3.contrib.securetransport: urllib3
+ urllib3.contrib.socks: urllib3
+ urllib3.exceptions: urllib3
+ urllib3.fields: urllib3
+ urllib3.filepost: urllib3
+ urllib3.packages: urllib3
+ urllib3.packages.__init__: urllib3
+ urllib3.packages.backports: urllib3
+ urllib3.packages.backports.__init__: urllib3
+ urllib3.packages.backports.makefile: urllib3
+ urllib3.packages.six: urllib3
+ urllib3.poolmanager: urllib3
+ urllib3.request: urllib3
+ urllib3.response: urllib3
+ urllib3.util: urllib3
+ urllib3.util.__init__: urllib3
+ urllib3.util.connection: urllib3
+ urllib3.util.proxy: urllib3
+ urllib3.util.queue: urllib3
+ urllib3.util.request: urllib3
+ urllib3.util.response: urllib3
+ urllib3.util.retry: urllib3
+ urllib3.util.ssl_: urllib3
+ urllib3.util.ssl_match_hostname: urllib3
+ urllib3.util.ssltransport: urllib3
+ urllib3.util.timeout: urllib3
+ urllib3.util.url: urllib3
+ urllib3.util.wait: urllib3
+ pip_repository:
+ name: gazelle_python_test
+integrity: 32e38932043eca090a64ca741758d8e4a5817c2cd7dc821fc927914c32fb3114
diff --git a/gazelle/python/testdata/with_third_party_requirements_from_imports/test.yaml b/gazelle/python/testdata/with_third_party_requirements_from_imports/test.yaml
new file mode 100644
index 0000000..fcea777
--- /dev/null
+++ b/gazelle/python/testdata/with_third_party_requirements_from_imports/test.yaml
@@ -0,0 +1,15 @@
+# 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.
+
+---
diff --git a/gazelle/pythonconfig/BUILD.bazel b/gazelle/pythonconfig/BUILD.bazel
new file mode 100644
index 0000000..d0f1690
--- /dev/null
+++ b/gazelle/pythonconfig/BUILD.bazel
@@ -0,0 +1,28 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
+
+go_library(
+ name = "pythonconfig",
+ srcs = [
+ "pythonconfig.go",
+ "types.go",
+ ],
+ importpath = "github.com/bazelbuild/rules_python/gazelle/pythonconfig",
+ visibility = ["//visibility:public"],
+ deps = [
+ "//manifest",
+ "@bazel_gazelle//label:go_default_library",
+ "@com_github_emirpasic_gods//lists/singlylinkedlist",
+ ],
+)
+
+go_test(
+ name = "pythonconfig_test",
+ srcs = ["pythonconfig_test.go"],
+ deps = [":pythonconfig"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//:__pkg__"],
+)
diff --git a/gazelle/pythonconfig/pythonconfig.go b/gazelle/pythonconfig/pythonconfig.go
new file mode 100644
index 0000000..c7cd7c1
--- /dev/null
+++ b/gazelle/pythonconfig/pythonconfig.go
@@ -0,0 +1,361 @@
+// 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.
+
+package pythonconfig
+
+import (
+ "fmt"
+ "path/filepath"
+ "strings"
+
+ "github.com/emirpasic/gods/lists/singlylinkedlist"
+
+ "github.com/bazelbuild/bazel-gazelle/label"
+ "github.com/bazelbuild/rules_python/gazelle/manifest"
+)
+
+// Directives
+const (
+ // PythonExtensionDirective represents the directive that controls whether
+ // this Python extension is enabled or not. Sub-packages inherit this value.
+ // Can be either "enabled" or "disabled". Defaults to "enabled".
+ PythonExtensionDirective = "python_extension"
+ // PythonRootDirective represents the directive that sets a Bazel package as
+ // a Python root. This is used on monorepos with multiple Python projects
+ // that don't share the top-level of the workspace as the root.
+ PythonRootDirective = "python_root"
+ // PythonManifestFileNameDirective represents the directive that overrides
+ // the default gazelle_python.yaml manifest file name.
+ PythonManifestFileNameDirective = "python_manifest_file_name"
+ // IgnoreFilesDirective represents the directive that controls the ignored
+ // files from the generated targets.
+ IgnoreFilesDirective = "python_ignore_files"
+ // IgnoreDependenciesDirective represents the directive that controls the
+ // ignored dependencies from the generated targets.
+ IgnoreDependenciesDirective = "python_ignore_dependencies"
+ // ValidateImportStatementsDirective represents the directive that controls
+ // whether the Python import statements should be validated.
+ ValidateImportStatementsDirective = "python_validate_import_statements"
+ // GenerationMode represents the directive that controls the target generation
+ // mode. See below for the GenerationModeType constants.
+ GenerationMode = "python_generation_mode"
+ // LibraryNamingConvention represents the directive that controls the
+ // py_library naming convention. It interpolates $package_name$ with the
+ // Bazel package name. E.g. if the Bazel package name is `foo`, setting this
+ // to `$package_name$_my_lib` would render to `foo_my_lib`.
+ LibraryNamingConvention = "python_library_naming_convention"
+ // BinaryNamingConvention represents the directive that controls the
+ // py_binary naming convention. See python_library_naming_convention for
+ // more info on the package name interpolation.
+ BinaryNamingConvention = "python_binary_naming_convention"
+ // TestNamingConvention represents the directive that controls the py_test
+ // naming convention. See python_library_naming_convention for more info on
+ // the package name interpolation.
+ TestNamingConvention = "python_test_naming_convention"
+)
+
+// GenerationModeType represents one of the generation modes for the Python
+// extension.
+type GenerationModeType string
+
+// Generation modes
+const (
+ // GenerationModePackage defines the mode in which targets will be generated
+ // for each __init__.py, or when an existing BUILD or BUILD.bazel file already
+ // determines a Bazel package.
+ GenerationModePackage GenerationModeType = "package"
+ // GenerationModeProject defines the mode in which a coarse-grained target will
+ // be generated englobing sub-directories containing Python files.
+ GenerationModeProject GenerationModeType = "project"
+)
+
+const (
+ packageNameNamingConventionSubstitution = "$package_name$"
+)
+
+// defaultIgnoreFiles is the list of default values used in the
+// python_ignore_files option.
+var defaultIgnoreFiles = map[string]struct{}{
+ "setup.py": {},
+}
+
+func SanitizeDistribution(distributionName string) string {
+ sanitizedDistribution := strings.ToLower(distributionName)
+ sanitizedDistribution = strings.ReplaceAll(sanitizedDistribution, "-", "_")
+ sanitizedDistribution = strings.ReplaceAll(sanitizedDistribution, ".", "_")
+
+ return sanitizedDistribution
+}
+
+// Configs is an extension of map[string]*Config. It provides finding methods
+// on top of the mapping.
+type Configs map[string]*Config
+
+// ParentForPackage returns the parent Config for the given Bazel package.
+func (c *Configs) ParentForPackage(pkg string) *Config {
+ dir := filepath.Dir(pkg)
+ if dir == "." {
+ dir = ""
+ }
+ parent := (map[string]*Config)(*c)[dir]
+ return parent
+}
+
+// Config represents a config extension for a specific Bazel package.
+type Config struct {
+ parent *Config
+
+ extensionEnabled bool
+ repoRoot string
+ pythonProjectRoot string
+ gazelleManifest *manifest.Manifest
+
+ excludedPatterns *singlylinkedlist.List
+ ignoreFiles map[string]struct{}
+ ignoreDependencies map[string]struct{}
+ validateImportStatements bool
+ coarseGrainedGeneration bool
+ libraryNamingConvention string
+ binaryNamingConvention string
+ testNamingConvention string
+}
+
+// New creates a new Config.
+func New(
+ repoRoot string,
+ pythonProjectRoot string,
+) *Config {
+ return &Config{
+ extensionEnabled: true,
+ repoRoot: repoRoot,
+ pythonProjectRoot: pythonProjectRoot,
+ excludedPatterns: singlylinkedlist.New(),
+ ignoreFiles: make(map[string]struct{}),
+ ignoreDependencies: make(map[string]struct{}),
+ validateImportStatements: true,
+ coarseGrainedGeneration: false,
+ libraryNamingConvention: packageNameNamingConventionSubstitution,
+ binaryNamingConvention: fmt.Sprintf("%s_bin", packageNameNamingConventionSubstitution),
+ testNamingConvention: fmt.Sprintf("%s_test", packageNameNamingConventionSubstitution),
+ }
+}
+
+// Parent returns the parent config.
+func (c *Config) Parent() *Config {
+ return c.parent
+}
+
+// NewChild creates a new child Config. It inherits desired values from the
+// current Config and sets itself as the parent to the child.
+func (c *Config) NewChild() *Config {
+ return &Config{
+ parent: c,
+ extensionEnabled: c.extensionEnabled,
+ repoRoot: c.repoRoot,
+ pythonProjectRoot: c.pythonProjectRoot,
+ excludedPatterns: c.excludedPatterns,
+ ignoreFiles: make(map[string]struct{}),
+ ignoreDependencies: make(map[string]struct{}),
+ validateImportStatements: c.validateImportStatements,
+ coarseGrainedGeneration: c.coarseGrainedGeneration,
+ libraryNamingConvention: c.libraryNamingConvention,
+ binaryNamingConvention: c.binaryNamingConvention,
+ testNamingConvention: c.testNamingConvention,
+ }
+}
+
+// AddExcludedPattern adds a glob pattern parsed from the standard
+// gazelle:exclude directive.
+func (c *Config) AddExcludedPattern(pattern string) {
+ c.excludedPatterns.Add(pattern)
+}
+
+// ExcludedPatterns returns the excluded patterns list.
+func (c *Config) ExcludedPatterns() *singlylinkedlist.List {
+ return c.excludedPatterns
+}
+
+// SetExtensionEnabled sets whether the extension is enabled or not.
+func (c *Config) SetExtensionEnabled(enabled bool) {
+ c.extensionEnabled = enabled
+}
+
+// ExtensionEnabled returns whether the extension is enabled or not.
+func (c *Config) ExtensionEnabled() bool {
+ return c.extensionEnabled
+}
+
+// SetPythonProjectRoot sets the Python project root.
+func (c *Config) SetPythonProjectRoot(pythonProjectRoot string) {
+ c.pythonProjectRoot = pythonProjectRoot
+}
+
+// PythonProjectRoot returns the Python project root.
+func (c *Config) PythonProjectRoot() string {
+ return c.pythonProjectRoot
+}
+
+// SetGazelleManifest sets the Gazelle manifest parsed from the
+// gazelle_python.yaml file.
+func (c *Config) SetGazelleManifest(gazelleManifest *manifest.Manifest) {
+ c.gazelleManifest = gazelleManifest
+}
+
+// FindThirdPartyDependency scans the gazelle manifests for the current config
+// and the parent configs up to the root finding if it can resolve the module
+// name.
+func (c *Config) FindThirdPartyDependency(modName string) (string, bool) {
+ for currentCfg := c; currentCfg != nil; currentCfg = currentCfg.parent {
+ if currentCfg.gazelleManifest != nil {
+ gazelleManifest := currentCfg.gazelleManifest
+ if distributionName, ok := gazelleManifest.ModulesMapping[modName]; ok {
+ var distributionRepositoryName string
+ if gazelleManifest.PipDepsRepositoryName != "" {
+ distributionRepositoryName = gazelleManifest.PipDepsRepositoryName
+ } else if gazelleManifest.PipRepository != nil {
+ distributionRepositoryName = gazelleManifest.PipRepository.Name
+ }
+ sanitizedDistribution := SanitizeDistribution(distributionName)
+
+ if gazelleManifest.PipRepository != nil && gazelleManifest.PipRepository.UsePipRepositoryAliases {
+ // @<repository_name>//<distribution_name>
+ lbl := label.New(distributionRepositoryName, sanitizedDistribution, sanitizedDistribution)
+ return lbl.String(), true
+ }
+
+ // @<repository_name>_<distribution_name>//:pkg
+ distributionRepositoryName = distributionRepositoryName + "_" + sanitizedDistribution
+ lbl := label.New(distributionRepositoryName, "", "pkg")
+ return lbl.String(), true
+ }
+ }
+ }
+ return "", false
+}
+
+// AddIgnoreFile adds a file to the list of ignored files for a given package.
+// Adding an ignored file to a package also makes it ignored on a subpackage.
+func (c *Config) AddIgnoreFile(file string) {
+ c.ignoreFiles[strings.TrimSpace(file)] = struct{}{}
+}
+
+// IgnoresFile checks if a file is ignored in the given package or in one of the
+// parent packages up to the workspace root.
+func (c *Config) IgnoresFile(file string) bool {
+ trimmedFile := strings.TrimSpace(file)
+
+ if _, ignores := defaultIgnoreFiles[trimmedFile]; ignores {
+ return true
+ }
+
+ if _, ignores := c.ignoreFiles[trimmedFile]; ignores {
+ return true
+ }
+
+ parent := c.parent
+ for parent != nil {
+ if _, ignores := parent.ignoreFiles[trimmedFile]; ignores {
+ return true
+ }
+ parent = parent.parent
+ }
+
+ return false
+}
+
+// AddIgnoreDependency adds a dependency to the list of ignored dependencies for
+// a given package. Adding an ignored dependency to a package also makes it
+// ignored on a subpackage.
+func (c *Config) AddIgnoreDependency(dep string) {
+ c.ignoreDependencies[strings.TrimSpace(dep)] = struct{}{}
+}
+
+// IgnoresDependency checks if a dependency is ignored in the given package or
+// in one of the parent packages up to the workspace root.
+func (c *Config) IgnoresDependency(dep string) bool {
+ trimmedDep := strings.TrimSpace(dep)
+
+ if _, ignores := c.ignoreDependencies[trimmedDep]; ignores {
+ return true
+ }
+
+ parent := c.parent
+ for parent != nil {
+ if _, ignores := parent.ignoreDependencies[trimmedDep]; ignores {
+ return true
+ }
+ parent = parent.parent
+ }
+
+ return false
+}
+
+// SetValidateImportStatements sets whether Python import statements should be
+// validated or not. It throws an error if this is set multiple times, i.e. if
+// the directive is specified multiple times in the Bazel workspace.
+func (c *Config) SetValidateImportStatements(validate bool) {
+ c.validateImportStatements = validate
+}
+
+// ValidateImportStatements returns whether the Python import statements should
+// be validated or not. If this option was not explicitly specified by the user,
+// it defaults to true.
+func (c *Config) ValidateImportStatements() bool {
+ return c.validateImportStatements
+}
+
+// SetCoarseGrainedGeneration sets whether coarse-grained targets should be
+// generated or not.
+func (c *Config) SetCoarseGrainedGeneration(coarseGrained bool) {
+ c.coarseGrainedGeneration = coarseGrained
+}
+
+// CoarseGrainedGeneration returns whether coarse-grained targets should be
+// generated or not.
+func (c *Config) CoarseGrainedGeneration() bool {
+ return c.coarseGrainedGeneration
+}
+
+// SetLibraryNamingConvention sets the py_library target naming convention.
+func (c *Config) SetLibraryNamingConvention(libraryNamingConvention string) {
+ c.libraryNamingConvention = libraryNamingConvention
+}
+
+// RenderLibraryName returns the py_library target name by performing all
+// substitutions.
+func (c *Config) RenderLibraryName(packageName string) string {
+ return strings.ReplaceAll(c.libraryNamingConvention, packageNameNamingConventionSubstitution, packageName)
+}
+
+// SetBinaryNamingConvention sets the py_binary target naming convention.
+func (c *Config) SetBinaryNamingConvention(binaryNamingConvention string) {
+ c.binaryNamingConvention = binaryNamingConvention
+}
+
+// RenderBinaryName returns the py_binary target name by performing all
+// substitutions.
+func (c *Config) RenderBinaryName(packageName string) string {
+ return strings.ReplaceAll(c.binaryNamingConvention, packageNameNamingConventionSubstitution, packageName)
+}
+
+// SetTestNamingConvention sets the py_test target naming convention.
+func (c *Config) SetTestNamingConvention(testNamingConvention string) {
+ c.testNamingConvention = testNamingConvention
+}
+
+// RenderTestName returns the py_test target name by performing all
+// substitutions.
+func (c *Config) RenderTestName(packageName string) string {
+ return strings.ReplaceAll(c.testNamingConvention, packageNameNamingConventionSubstitution, packageName)
+}
diff --git a/gazelle/pythonconfig/pythonconfig_test.go b/gazelle/pythonconfig/pythonconfig_test.go
new file mode 100644
index 0000000..1512eb9
--- /dev/null
+++ b/gazelle/pythonconfig/pythonconfig_test.go
@@ -0,0 +1,28 @@
+package pythonconfig
+
+import (
+ "testing"
+
+ "github.com/bazelbuild/rules_python/gazelle/pythonconfig"
+)
+
+func TestDistributionSanitizing(t *testing.T) {
+ tests := map[string]struct {
+ input string
+ want string
+ }{
+ "upper case": {input: "DistWithUpperCase", want: "distwithuppercase"},
+ "dashes": {input: "dist-with-dashes", want: "dist_with_dashes"},
+ "dots": {input: "dist.with.dots", want: "dist_with_dots"},
+ "mixed": {input: "To-be.sanitized", want: "to_be_sanitized"},
+ }
+
+ for name, tc := range tests {
+ t.Run(name, func(t *testing.T) {
+ got := pythonconfig.SanitizeDistribution(tc.input)
+ if tc.want != got {
+ t.Fatalf("expected %q, got %q", tc.want, got)
+ }
+ })
+ }
+}
diff --git a/gazelle/pythonconfig/types.go b/gazelle/pythonconfig/types.go
new file mode 100644
index 0000000..d83d35f
--- /dev/null
+++ b/gazelle/pythonconfig/types.go
@@ -0,0 +1,117 @@
+// 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.
+
+package pythonconfig
+
+import (
+ "fmt"
+ "sort"
+ "strings"
+)
+
+// StringSet satisfies the flag.Value interface. It constructs a set backed by
+// a hashmap by parsing the flag string value using the provided separator.
+type StringSet struct {
+ set map[string]struct{}
+ separator string
+}
+
+// NewStringSet constructs a new StringSet with the given separator.
+func NewStringSet(separator string) *StringSet {
+ return &StringSet{
+ set: make(map[string]struct{}),
+ separator: separator,
+ }
+}
+
+// String satisfies flag.Value.String.
+func (ss *StringSet) String() string {
+ keys := make([]string, 0, len(ss.set))
+ for key := range ss.set {
+ keys = append(keys, key)
+ }
+ return fmt.Sprintf("%v", sort.StringSlice(keys))
+}
+
+// Set satisfies flag.Value.Set.
+func (ss *StringSet) Set(s string) error {
+ list := strings.Split(s, ss.separator)
+ for _, v := range list {
+ trimmed := strings.TrimSpace(v)
+ if trimmed == "" {
+ continue
+ }
+ ss.set[trimmed] = struct{}{}
+ }
+ return nil
+}
+
+// Contains returns whether the StringSet contains the provided element or not.
+func (ss *StringSet) Contains(s string) bool {
+ _, contains := ss.set[s]
+ return contains
+}
+
+// StringMapList satisfies the flag.Value interface. It constructs a string map
+// by parsing the flag string value using the provided list and map separators.
+type StringMapList struct {
+ mapping map[string]string
+ listSeparator string
+ mapSeparator string
+}
+
+// NewStringMapList constructs a new StringMapList with the given separators.
+func NewStringMapList(listSeparator, mapSeparator string) *StringMapList {
+ return &StringMapList{
+ mapping: make(map[string]string),
+ listSeparator: listSeparator,
+ mapSeparator: mapSeparator,
+ }
+}
+
+// String satisfies flag.Value.String.
+func (sml *StringMapList) String() string {
+ return fmt.Sprintf("%v", sml.mapping)
+}
+
+// Set satisfies flag.Value.Set.
+func (sml *StringMapList) Set(s string) error {
+ list := strings.Split(s, sml.listSeparator)
+ for _, v := range list {
+ trimmed := strings.TrimSpace(v)
+ if trimmed == "" {
+ continue
+ }
+ mapList := strings.SplitN(trimmed, sml.mapSeparator, 2)
+ if len(mapList) < 2 {
+ return fmt.Errorf(
+ "%q is not a valid map using %q as a separator",
+ trimmed, sml.mapSeparator,
+ )
+ }
+ key := mapList[0]
+ if _, exists := sml.mapping[key]; exists {
+ return fmt.Errorf("key %q already set", key)
+ }
+ val := mapList[1]
+ sml.mapping[key] = val
+ }
+ return nil
+}
+
+// Get returns the value for the given key.
+func (sml *StringMapList) Get(key string) (string, bool) {
+ val, exists := sml.mapping[key]
+ return val, exists
+} \ No newline at end of file
diff --git a/internal_deps.bzl b/internal_deps.bzl
new file mode 100644
index 0000000..f50d2bf
--- /dev/null
+++ b/internal_deps.bzl
@@ -0,0 +1,184 @@
+# 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.
+
+"""Dependencies that are needed for rules_python tests and tools."""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+
+def rules_python_internal_deps():
+ """Fetches all required dependencies for rules_python tests and tools."""
+
+ # This version is also used in python/tests/toolchains/workspace_template/WORKSPACE.tmpl
+ # and tests/ignore_root_user_error/WORKSPACE.
+ # If you update this dependency, please update the tests as well.
+ maybe(
+ http_archive,
+ name = "bazel_skylib",
+ sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d",
+ urls = [
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ ],
+ )
+
+ maybe(
+ http_archive,
+ name = "rules_pkg",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz",
+ "https://github.com/bazelbuild/rules_pkg/releases/download/0.7.0/rules_pkg-0.7.0.tar.gz",
+ ],
+ sha256 = "8a298e832762eda1830597d64fe7db58178aa84cd5926d76d5b744d6558941c2",
+ )
+
+ maybe(
+ http_archive,
+ name = "rules_testing",
+ sha256 = "8df0a8eb21739ea4b0a03f5dc79e68e245a45c076cfab404b940cc205cb62162",
+ strip_prefix = "rules_testing-0.4.0",
+ url = "https://github.com/bazelbuild/rules_testing/releases/download/v0.4.0/rules_testing-v0.4.0.tar.gz",
+ )
+
+ maybe(
+ http_archive,
+ name = "rules_license",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_license/releases/download/0.0.7/rules_license-0.0.7.tar.gz",
+ "https://github.com/bazelbuild/rules_license/releases/download/0.0.7/rules_license-0.0.7.tar.gz",
+ ],
+ sha256 = "4531deccb913639c30e5c7512a054d5d875698daeb75d8cf90f284375fe7c360",
+ )
+
+ maybe(
+ http_archive,
+ name = "io_bazel_stardoc",
+ url = "https://github.com/bazelbuild/stardoc/archive/6f274e903009158504a9d9130d7f7d5f3e9421ed.tar.gz",
+ sha256 = "b5d6891f869d5b5a224316ec4dd9e9d481885a9b1a1c81eb846e20180156f2fa",
+ strip_prefix = "stardoc-6f274e903009158504a9d9130d7f7d5f3e9421ed",
+ )
+
+ # The below two deps are required for the integration test with bazel
+ # gazelle. Maybe the test should be moved to the `gazelle` workspace?
+ maybe(
+ http_archive,
+ name = "io_bazel_rules_go",
+ sha256 = "6dc2da7ab4cf5d7bfc7c949776b1b7c733f05e56edc4bcd9022bb249d2e2a996",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.39.1/rules_go-v0.39.1.zip",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.39.1/rules_go-v0.39.1.zip",
+ ],
+ )
+
+ maybe(
+ http_archive,
+ name = "bazel_gazelle",
+ sha256 = "727f3e4edd96ea20c29e8c2ca9e8d2af724d8c7778e7923a854b2c80952bc405",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.30.0/bazel-gazelle-v0.30.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.30.0/bazel-gazelle-v0.30.0.tar.gz",
+ ],
+ )
+
+ # Test data for WHL tool testing.
+ maybe(
+ http_file,
+ name = "futures_2_2_0_whl",
+ downloaded_file_path = "futures-2.2.0-py2.py3-none-any.whl",
+ sha256 = "9fd22b354a4c4755ad8c7d161d93f5026aca4cfe999bd2e53168f14765c02cd6",
+ # From https://pypi.python.org/pypi/futures/2.2.0
+ urls = [
+ "https://mirror.bazel.build/pypi.python.org/packages/d7/1d/68874943aa37cf1c483fc61def813188473596043158faa6511c04a038b4/futures-2.2.0-py2.py3-none-any.whl",
+ "https://pypi.python.org/packages/d7/1d/68874943aa37cf1c483fc61def813188473596043158faa6511c04a038b4/futures-2.2.0-py2.py3-none-any.whl",
+ ],
+ )
+
+ maybe(
+ http_file,
+ name = "futures_3_1_1_whl",
+ downloaded_file_path = "futures-3.1.1-py2-none-any.whl",
+ sha256 = "c4884a65654a7c45435063e14ae85280eb1f111d94e542396717ba9828c4337f",
+ # From https://pypi.python.org/pypi/futures
+ urls = [
+ "https://mirror.bazel.build/pypi.python.org/packages/a6/1c/72a18c8c7502ee1b38a604a5c5243aa8c2a64f4bba4e6631b1b8972235dd/futures-3.1.1-py2-none-any.whl",
+ "https://pypi.python.org/packages/a6/1c/72a18c8c7502ee1b38a604a5c5243aa8c2a64f4bba4e6631b1b8972235dd/futures-3.1.1-py2-none-any.whl",
+ ],
+ )
+
+ maybe(
+ http_file,
+ name = "google_cloud_language_whl",
+ downloaded_file_path = "google_cloud_language-0.29.0-py2.py3-none-any.whl",
+ sha256 = "a2dd34f0a0ebf5705dcbe34bd41199b1d0a55c4597d38ed045bd183361a561e9",
+ # From https://pypi.python.org/pypi/google-cloud-language
+ urls = [
+ "https://mirror.bazel.build/pypi.python.org/packages/6e/86/cae57e4802e72d9e626ee5828ed5a646cf4016b473a4a022f1038dba3460/google_cloud_language-0.29.0-py2.py3-none-any.whl",
+ "https://pypi.python.org/packages/6e/86/cae57e4802e72d9e626ee5828ed5a646cf4016b473a4a022f1038dba3460/google_cloud_language-0.29.0-py2.py3-none-any.whl",
+ ],
+ )
+
+ maybe(
+ http_file,
+ name = "grpc_whl",
+ downloaded_file_path = "grpcio-1.6.0-cp27-cp27m-manylinux1_i686.whl",
+ sha256 = "c232d6d168cb582e5eba8e1c0da8d64b54b041dd5ea194895a2fe76050916561",
+ # From https://pypi.python.org/pypi/grpcio/1.6.0
+ urls = [
+ "https://mirror.bazel.build/pypi.python.org/packages/c6/28/67651b4eabe616b27472c5518f9b2aa3f63beab8f62100b26f05ac428639/grpcio-1.6.0-cp27-cp27m-manylinux1_i686.whl",
+ "https://pypi.python.org/packages/c6/28/67651b4eabe616b27472c5518f9b2aa3f63beab8f62100b26f05ac428639/grpcio-1.6.0-cp27-cp27m-manylinux1_i686.whl",
+ ],
+ )
+
+ maybe(
+ http_file,
+ name = "mock_whl",
+ downloaded_file_path = "mock-2.0.0-py2.py3-none-any.whl",
+ sha256 = "5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
+ # From https://pypi.python.org/pypi/mock
+ urls = [
+ "https://mirror.bazel.build/pypi.python.org/packages/e6/35/f187bdf23be87092bd0f1200d43d23076cee4d0dec109f195173fd3ebc79/mock-2.0.0-py2.py3-none-any.whl",
+ "https://pypi.python.org/packages/e6/35/f187bdf23be87092bd0f1200d43d23076cee4d0dec109f195173fd3ebc79/mock-2.0.0-py2.py3-none-any.whl",
+ ],
+ )
+
+ maybe(
+ http_archive,
+ name = "build_bazel_integration_testing",
+ urls = [
+ "https://github.com/bazelbuild/bazel-integration-testing/archive/165440b2dbda885f8d1ccb8d0f417e6cf8c54f17.zip",
+ ],
+ strip_prefix = "bazel-integration-testing-165440b2dbda885f8d1ccb8d0f417e6cf8c54f17",
+ sha256 = "2401b1369ef44cc42f91dc94443ef491208dbd06da1e1e10b702d8c189f098e3",
+ )
+
+ maybe(
+ http_archive,
+ name = "rules_proto",
+ sha256 = "dc3fb206a2cb3441b485eb1e423165b231235a1ea9b031b4433cf7bc1fa460dd",
+ strip_prefix = "rules_proto-5.3.0-21.7",
+ urls = [
+ "https://github.com/bazelbuild/rules_proto/archive/refs/tags/5.3.0-21.7.tar.gz",
+ ],
+ )
+
+ maybe(
+ http_archive,
+ name = "com_google_protobuf",
+ sha256 = "75be42bd736f4df6d702a0e4e4d30de9ee40eac024c4b845d17ae4cc831fe4ae",
+ strip_prefix = "protobuf-21.7",
+ urls = [
+ "https://mirror.bazel.build/github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz",
+ "https://github.com/protocolbuffers/protobuf/archive/v21.7.tar.gz",
+ ],
+ )
diff --git a/internal_setup.bzl b/internal_setup.bzl
new file mode 100644
index 0000000..c3a7ad4
--- /dev/null
+++ b/internal_setup.bzl
@@ -0,0 +1,38 @@
+# Copyright 2022 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.
+
+"""Setup for rules_python tests and tools."""
+
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+load("@build_bazel_integration_testing//tools:repositories.bzl", "bazel_binaries")
+load("@com_google_protobuf//:protobuf_deps.bzl", "protobuf_deps")
+load("@rules_proto//proto:repositories.bzl", "rules_proto_dependencies", "rules_proto_toolchains")
+load("//:version.bzl", "SUPPORTED_BAZEL_VERSIONS")
+load("//python/pip_install:repositories.bzl", "pip_install_dependencies")
+
+def rules_python_internal_setup():
+ """Setup for rules_python tests and tools."""
+
+ # Because we don't use the pip_install rule, we have to call this to fetch its deps
+ pip_install_dependencies()
+
+ # Depend on the Bazel binaries for running bazel-in-bazel tests
+ bazel_binaries(versions = SUPPORTED_BAZEL_VERSIONS)
+
+ bazel_skylib_workspace()
+
+ rules_proto_dependencies()
+ rules_proto_toolchains()
+
+ protobuf_deps()
diff --git a/proposals/2018-10-25-selecting-between-python-2-and-3.md b/proposals/2018-10-25-selecting-between-python-2-and-3.md
new file mode 100644
index 0000000..e731f97
--- /dev/null
+++ b/proposals/2018-10-25-selecting-between-python-2-and-3.md
@@ -0,0 +1,136 @@
+---
+title: Selecting Between Python 2 and 3
+status: Accepted
+created: 2018-10-25
+updated: 2019-01-11
+authors:
+ - [brandjon@](https://github.com/brandjon)
+reviewers:
+ - [mrovner@](https://github.com/mrovner)
+discussion thread: [bazel #6583](https://github.com/bazelbuild/bazel/issues/6583)
+---
+
+# Selecting Between Python 2 and 3
+
+## Abstract
+
+The "Python mode" configuration value controls whether Python 2 or Python 3 is used to run Python targets built by Bazel. This design document reviews the existing mechanisms for setting the Python mode (the "tri-state model") and describes a simplified mechanism that should replace it (the "boolean model").
+
+Links to Github issues are given where applicable. See also [bazel #6444](https://github.com/bazelbuild/bazel/issues/6444) for a tracking list of Python mode issues.
+
+Throughout, when we say `py_binary`, we also mean to include `py_test`.
+
+## Background
+
+The Python mode controls whether a Python 2 or 3 interpreter is used to run a `py_binary` that is built by Bazel.
+
+* When no `py_runtime` is supplied (via `--python_top`), the mode should control whether the command `python2` or `python3` is embedded into the generated wrapper script ([bazel #4815](https://github.com/bazelbuild/bazel/issues/4815)).
+
+* In a future design for a "`py_toolchain`"-type rule, a pair of interpreter targets will be bundled together as a toolchain, and the mode will control which one gets their full path embedded into this script.
+
+The Python mode is also used to help validate that Python source code annotated with `srcs_version` is used appropriately: If a Python target has the `srcs_version` attribute set to `PY2` or `PY3` rather than to `PY2AND3` (the default), it can only be depended on by targets built in Python 2 or Python 3 mode respectively.
+
+Whenever the same Bazel target can be built in multiple configurations within a single build, it is necessary to write the output artifacts of different versions of the target to different paths. Otherwise the build fails with an "action conflict" error -- Bazel's way of avoiding a correctness bug. For Python targets, and more broadly for targets that may transitively depend on Python targets, this means that different output path roots must be used for different Python modes.
+
+## Out-of-scope generalizations
+
+It is possible to imagine extending the Python mode and `srcs_version` so that it can check for compatibility with minor releases (ex: "Python 3.7"), patch releases ("Python 3.7.1"), alternative interpreters ("CPython" or "PyPy"), and exclude known bad releases. We decline to do so because this treads into generalized constraint checking, which may be better handled in the future by the [platforms and toolchain framework](https://docs.bazel.build/versions/master/toolchains.html).
+
+Compared to these other kinds of version checks, Python 2 vs. 3 is a more compelling use case to support with dedicated machinery. The incompatibilities between these versions are more severe. In many code bases there is an ongoing effort to migrate from 2 to 3, while in others there exists Python 2 code that will never be migrated and must be supported indefinitely.
+
+## Tri-state model
+
+Under the existing tri-state model, the Python mode can take on three values: `PY2`, `PY3`, and `null`. The first two modes can be triggered by the `--force_python` flag on the command line or by the `default_python_version` attribute on `py_binary` rules. The `null` mode is the default state when neither the flag nor `default_python_version` is specified. `select()` expressions can distinguish between these states by using `config_setting`s that test the value of `force_python` (where `null` is matched by `//conditions:default`).
+
+The Python mode is "sticky"; once it is set to `PY2` or `PY3`, it stays that way for all subsequent targets. For a `py_binary` target, this means that all transitive dependencies of the target are built with the same mode as the target itself. For the `--force_python` flag, this means that if the flag is given, it applies universally to the entire build invocation, regardless of the `default_python_version` attributes of any Python targets (hence the "default" in the attribute's name).
+
+### Data dependencies
+
+In principle the Python mode needs to propagate to any `py_library` targets that are transitively in the `deps` attribute. Conceptually, this corresponds to enforcing that a Python binary cannot `import` a module written for a different version of Python than the currently running interpreter. But there is no need to propagate the mode across the `data` attribute, which often corresponds to one Python binary calling another as a separate process.
+
+In order to facilitate `PY3` binaries that depend on `PY2` ones and vice versa, the tri-state model needs to be modified so that the mode is reset to `null` for `data` attributes ([bazel #6441](https://github.com/bazelbuild/bazel/issues/6441)). But it's not clear exactly which attributes should trigger a reset. For example, suppose a Python source file is generated by a `genrule`: Then the `genrule` shouldn't propagate any Python mode to any of its attributes, even though it appears in the transitive closure of a `py_binary`'s `deps`. One could imagine resetting the mode across every attribute except those in a small whitelist (`deps` of `py_binary`, `py_test`, and `py_library`), but this would require new functionality in Bazel and possibly interact poorly with Starlark-defined rules.
+
+### Output roots
+
+Since targets that are built for Python 3 produce different results than those built for Python 2, the outputs for these two configurations must be kept separate in order to avoid action conflicts. Therefore, targets built in `PY3` mode get placed under an output root that includes the string "`-py3`".
+
+Currently, targets that are built in the `null` mode default to using Python 2. Counterintuitively, there is a subtle distinction between building a target in `null` mode and `PY2` mode: Even though the same interpreter is used for the top-level target, the target's transitive dependencies may behave differently, for instance if a `select()` on `force_python` is used. This means that using both `PY2` and `null` for the same target can result in action conflicts ([bazel #6501](https://github.com/bazelbuild/bazel/issues/6501)). However, due to a bug it is not yet possible to have both `PY2` and `null` modes within the same build invocation.
+
+Under the tri-state model, the most straightforward solution for these action conflicts is to use a separate "`-py2`" root for `PY2` mode. This would mean that the same target could be built in not two but three different configurations, corresponding to the three different modes, even though there are only two distinct Python versions. A more complicated alternative would be to prohibit `select()` from being able to distinguish `null` from `PY2`, in order to help ensure that building an arbitrary target in both of these modes does not succeed with different results.
+
+### Libraries at the top level
+
+Currently the mode is only changed by `--force_python` and by `py_binary`. This means that when you build a `py_library` at the top level (that is, specifying it directly on the build command line) without a `--force_python` flag, the library gets the `null` mode, which means Python 2 by default. This causes an error if the library has `srcs_python` set to `PY3`. This in turn means you cannot run a flagless build command on a wildcard pattern, such as `bazel build :all` or `bazel build ...`, if any of the targets in the package(s) contains a Python 3-only library target. Worse, if there are both a Python 2-only library and a Python 3-only library, even specifying `--force_python` can't make the wildcard build work.
+
+In the tri-state model, this can be addressed by allowing `py_library` to change the mode from `null` to either `PY2` or `PY3` based on whichever version is compatible with its `srcs_version` attribute. This was a proposed fix for [bazel #1446](https://github.com/bazelbuild/bazel/issues/1446).
+
+## Boolean model
+
+Under the boolean model, `null` is eliminated as a valid value for the Python mode. Instead, the mode will immediately default to either `PY2` or `PY3`. The mode is no longer sticky, but changes as needed whenever a new `py_binary` target is reached.
+
+Since there is no longer a third value corresponding to "uncommitted", a target can no longer tell whether it was set to `PY2` mode explicitly (by a flag or a `py_binary`), or if it was set by default because no mode was specified. The current version will be inspectable using `config_setting` to read a setting whose value is always one of `"PY2"` or `"PY3"`.
+
+### Data dependencies
+
+Since `py_binary` will now change the mode as needed, there is no need to explicitly reset the mode to a particular value (`null`) when crossing `data` attributes. Python 3 targets can freely depend on Python 2 targets and vice versa, so long as the dependency is not via the `deps` attribute in a way that violates `srcs_version` validation (see below).
+
+### Output roots
+
+Since there are only two modes, there need only be two output roots. This avoids action conflicts without resorting to creating a redundant third output root, or trying to coerce two similar-but-distinct modes to map onto the same output root.
+
+Since the mode is not being reset across data dependencies, it is possible that compared to the tri-state model, the boolean model causes some data dependencies to be built in two configurations instead of just one. This is considered to be an acceptable tradeoff of the boolean model. Note that there exist other cases where redundant rebuilding occurs regardless of which model we use.
+
+### Libraries at the top level
+
+We want to be able to build a `py_library` at the top level without having to specify the correct mode. At the same time, we still want `srcs_version` to validate that a `py_binary` only depends on `py_library`s that are compatible with its mode. The way to achieve this is to move validation from within the `py_library` rule up to the `py_binary` rule.
+
+We add two new boolean fields to a provider returned by `py_library`. This bools correspond to whether or not there are any Python 2-only and Python 3-only sources (respectively) in the library's transitive closure. It is easy to compute these bits as boolean ORs as the providers are merged. `py_binary` simply checks these bits against its own Python mode.
+
+It is important that when `py_binary` detects a version conflict, the user is given the label of one or more transitive dependencies that introduced the constraint. There are several ways to implement this, such as:
+
+- additional provider fields to propagate context to the error message
+- an aspect that traverses the dependencies of the `py_binary`
+- emitting warning messages at conflicting `py_library` targets
+
+The choice of which approach to use is outside the scope of this proposal.
+
+It is possible that a library is only ever used by Python 3 binaries, but when the library is built as part of a `bazel build :all` command it gets the Python 2 mode by default. This happens even if the library is annotated with `srcs_version` set to `PY3`. Generally this should cause no harm aside from some repeated build work. In the future we can add the same version attribute that `py_binary` has to `py_library`, so the target definition can be made unambiguous.
+
+Aside from failures due to validation, there is currently a bug whereby building a `PY2` library in `PY3` mode can invoke a stub wrapper that fails ([bazel #1393](https://github.com/bazelbuild/bazel/issues/1393)). We will remove the stub and the behavior that attempted to call it.
+
+## API changes
+
+The attribute `default_python_version` of `py_binary` is renamed to `python_version`. The flag `--force_python` is renamed to `--python_version`. (An alternative naming scheme would have been to use "python_major_version", but this is more verbose and inconsistent with `srcs_version`.)
+
+The Python mode becomes "non-sticky" and `srcs_version` validation becomes less strict. Building a `py_library` target directly will not trigger validation. Building a `py_binary` that depends on a `py_library` having an incompatible version will only fail if the dependency occurs via transitive `deps`, and not when it occurs via other paths such as a `data` dep or a `genrule` that produces a source file.
+
+The `"py"` provider of Python rules gains two new boolean fields, `has_py2_only_sources` and `has_py3_only_sources`. Existing Python rules are updated to set these fields. Dependencies of Python rules that do not have the `"py"` provider, or those fields on that provider, are treated as if the value of the fields is `False`.
+
+A new `select()`-able target is created at `@bazel_tools//tools/python:python_version` to return the current Python mode. It can be used in the `flag_values` attribute of `config_setting` and always equals either `"PY2"` or `"PY3"`. (In the future this flag may be moved out of `@bazel_tools` and into `bazelbuild/rules_python`. It may also be made into a `build_setting` so that it can replace the native `--python_version` flag.) It is disallowed to use `"python_version"` in a `config_setting`.
+
+The flag `--host_force_python` is unaffected by this doc, except that it becomes illegal to use it in a `config_setting`.
+
+## Migration and compatibility
+
+The rollout and migration of the new features are split into two groups, syntactic and semantic.
+
+For syntax, the new `--python_version` flag and `python_version` attribute are available immediately, and behave exactly the same as the old flag and attribute. When both the new and old flags are present on the command line, or both the new and old attributes are present on the same target, the new one takes precedence and the old is ignored. The `@bazel_tools//tools/python:python_version` target is also available unconditionally.
+
+A migration flag `--incompatible_remove_old_python_version_api` makes unavailable the `--force_python` flag and `default_python_version` attribute, and disallows `select()`-ing on `"force_python"` and `"host_force_python"`.
+
+For semantics, a flag `--incompatible_allow_python_version_transitions` makes Bazel use the new non-sticky version transitions and the deferred `srcs_version` validation. This applies regardless of whether the new or old API is used to specify the Python version. The new `"py"` provider fields are created regardless of which flags are given.
+
+Migrating for `--incompatible_remove_old_python_version_api` guarantees that the Python version only ever has two possible values. Migrating for `--incompatible_allow_python_version_transitions` enables data dependencies across different versions of Python. It is recommended to do the API migration first in order to avoid action conflicts.
+
+Strictly speaking, Python 3 support is currently marked "experimental" in documentation, so in theory we may be able to make these changes without introducing new incompatible and experimental flags. However these changes will likely affect many users of the Python rules, so flags would be more user-friendly. Bazel is also transitioning to a policy wherein all experimental APIs must be flag-guarded, regardless of any disclaimers in their documentation.
+
+## Changelog
+
+Date | Change
+------------ | ------
+2018-10-25 | Initial version
+2018-11-02 | Refine migration path
+2018-12-17 | Refine plan for `select()`
+2018-12-19 | Refine plan for `select()` again
+2019-01-10 | Refine migration path
+2019-01-11 | Formal approval and update provider fields
diff --git a/proposals/2018-11-08-customizing-the-python-stub-template.md b/proposals/2018-11-08-customizing-the-python-stub-template.md
new file mode 100644
index 0000000..5b9d878
--- /dev/null
+++ b/proposals/2018-11-08-customizing-the-python-stub-template.md
@@ -0,0 +1,47 @@
+---
+title: Customizing the Python Stub Template
+status: Draft, not yet ready for review
+created: 2018-11-08
+updated: 2018-11-09
+authors:
+ - [brandjon@](https://github.com/brandjon)
+reviewers:
+ - [gpshead@](https://github.com/gpshead)
+discussion thread: [bazel #137](https://github.com/bazelbuild/bazel/issues/137)
+---
+
+# Customizing the Python Stub Template
+
+## Abstract
+
+This design document proposes a way to use a different Python stub template, so that users can control how the Python interpreter gets invoked to run their targets.
+
+**Open questions:** It is not currently clear whether the use cases warrant this kind of expressivity, or whether users can get by with smaller, more narrowly focused ways of parameterizing the existing stub template. The exact stub API is also to be determined.
+
+## Background
+
+The usual executable artifact of a `py_binary` rule is a Python stub script. This script manipulates the Python environment to set up the module import path and make the runfiles available, before passing control to the underlying user Python program. The stub script is generated from a [stub template](https://github.com/bazelbuild/bazel/blob/ef0024b831a71521390dcb837b24b86485e5998d/src/main/java/com/google/devtools/build/lib/bazel/rules/python/python_stub_template.txt) by [instantiating some placeholders](https://github.com/bazelbuild/bazel/blob/ef0024b831a71521390dcb837b24b86485e5998d/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java#L152-L159).
+
+Generally the Python stub and user program is executed using the system Python interpreter of the target platform. Although this is non-hermetic, the details of the interpreter can be reified by a [`py_runtime`](https://docs.bazel.build/versions/master/be/python.html#py_runtime) target. In the future this will allow for platform-aware selection of an appropriate Python interpreter using the [toolchain](https://docs.bazel.build/versions/master/toolchains.html) framework.
+
+## Proposal
+
+A new `Label`-valued attribute, `stub_template`, is added to `py_runtime`. This label points to a file; by default it is `//tools/python:python_stub_template.txt`, which is the renamed location of the existing template. The `py_runtime` rule will resolve this label to an `Artifact` and propagate it in a new field of [`BazelPyRuntimeProvider`](https://github.com/bazelbuild/bazel/blob/1f684e1b87cd8881a0a4b33e86ba66743e32d674/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPyRuntimeProvider.java). [`BazelPythonSemantics#createExecutable`](https://github.com/bazelbuild/bazel/blob/ef0024b831a71521390dcb837b24b86485e5998d/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java#L130) will refer to this `Artifact` instead of retrieving the template as a Java resource file.
+
+It is not yet decided which template placeholders are specified, or whether the placeholders will remain an experimental API for the moment.
+
+## Original approach
+
+An earlier proposed approach (suggested on the discussion thread, and implemented by [fahhem@](https://github.com/fahhem)) was to add the `stub_template` attribute to `py_binary` rather than to `py_runtime`.
+
+This would make it trivial to customize the stub for an individual Python target without affecting the other targets in the build. This could be useful if there were a one-off target that had special requirements.
+
+However, the author believes that the stub is more naturally tied to the Python interpreter than to an individual target. Putting the attribute on `py_runtime` makes it easy to affect all Python targets that use the same interpreter. It also allows the same Python target to use different stubs depending on which interpreter it is built for -- for instance, the same target can have different stubs on different platforms.
+
+If it is necessary to use a custom stub for a particular target, that could still be achieved by making that one target use a different `py_runtime`. This isn't possible at the moment but will be when a `py_toolchain` rule is added.
+
+## Changelog
+
+Date | Change
+------------ | ------
+2018-11-08 | Initial version
diff --git a/proposals/2019-02-12-design-for-a-python-toolchain.md b/proposals/2019-02-12-design-for-a-python-toolchain.md
new file mode 100644
index 0000000..0d45866
--- /dev/null
+++ b/proposals/2019-02-12-design-for-a-python-toolchain.md
@@ -0,0 +1,247 @@
+---
+title: Design for a Python Toolchain
+status: Accepted
+created: 2019-02-12
+updated: 2019-02-21
+authors:
+ - [brandjon@](https://github.com/brandjon)
+reviewers:
+ - [katre@](https://github.com/katre), [mrovner@](https://github.com/mrovner), [nlopezgi@](https://github.com/nlopezgi)
+discussion thread: [bazel #7375](https://github.com/bazelbuild/bazel/issues/7375)
+---
+
+# Design for a Python Toolchain
+
+## Abstract
+
+This doc outlines the design of a Python toolchain rule and its associated machinery. Essentially a new `py_runtime_pair` toolchain rule is created to wrap two `py_runtime` targets (one for Python 2 and one for Python 3), thereby making runtimes discoverable via [toolchain resolution](https://docs.bazel.build/versions/master/toolchains.html). This replaces the previous mechanism of explicitly specifying a global runtime via `--python_top` or `--python_path`; those flags are now deprecated.
+
+The new toolchain-related definitions are implemented in Starlark. A byproduct of this is that the provider type for `py_runtime` is exposed to Starlark. We also add to `py_runtime` an attribute for declaring whether it represents a Python 2 or Python 3 runtime.
+
+## Motivation
+
+The goal is to make the native Python rules use the toolchain framework to resolve the Python runtime. Advantages include:
+
+* allowing each `py_binary` to use a runtime suitable for its target platform
+
+* allowing Python 2 and Python 3 targets to run in the same build without [hacks](https://github.com/bazelbuild/bazel/issues/4815#issuecomment-460777113)
+
+* making it easier to run Python-related builds under remote execution
+
+* adding support for autodetection of available system Python runtimes, without requiring ad hoc rule logic
+
+* removing `--python_top` and `--python_path`
+
+* bringing Python in line with other rule sets and Bazel's best practices
+
+**Non-goal:** This work does not allow individual `py_binary`s to directly name a Python runtime to use. Instead, this information should be worked into either the configuration or a future toolchain constraint system. See the FAQ, below.
+
+## Design
+
+### New definitions
+
+A new [toolchain type](https://docs.bazel.build/versions/master/toolchains.html#writing-rules-that-use-toolchains) is created at `@bazel_tools//tools/python:toolchain_type`. This is the type for toolchains that provide a way to run Python code.
+
+Toolchain rules of this type are expected to return a [`ToolchainInfo`](https://docs.bazel.build/versions/master/skylark/lib/ToolchainInfo.html) with two fields, `py2_runtime` and `py3_runtime`, each of type `PyRuntimeInfo`. They are used for `PY2` and `PY3` binaries respectively.
+
+```python
+def _some_python_toolchain_impl(ctx):
+ ...
+ return [platform_common.ToolchainInfo(
+ py2_runtime = PyRuntimeInfo(...),
+ py3_runtime = PyRuntimeInfo(...))]
+```
+
+If either Python 2 or Python 3 is not provided by the toolchain, the corresponding field may be set to `None`. This is strongly discouraged, as it will prevent any target relying on that toolchain from using that version of Python. Toolchains that do use `None` here should be registered with lower priority than other toolchains, so that they are chosen only as a fallback.
+
+`PyRuntimeInfo` is the newly-exposed Starlark name of the native provider returned by the [`py_runtime`](https://docs.bazel.build/versions/master/be/python.html#py_runtime) rule. Like `PyInfo`, it is a top-level built-in name. Also like `PyInfo` and the native Python rules, it will eventually be migrated to Starlark and moved out of the Bazel repository.
+
+A `PyRuntimeInfo` describes either a *platform runtime* or an *in-build runtime*. A platform runtime accesses a system-installed interpreter at a known path, whereas an in-build runtime points to a build target that acts as the interpreter. In both cases, an "interpreter" is really any executable binary or wrapper script that is capable of running a Python script passed on the command line, following the same conventions as the standard CPython interpreter. Note that any platform runtime imposes a requirement on the target platform. Therefore, any toolchain returning such a `PyRuntimeInfo` should include a corresponding target platform constraint, to ensure it cannot be selected for a platform that does not have the interpreter at that path. Even an in-build runtime can require platform constraints, for instance in the case of a wrapper script that invokes the system interpreter.
+
+We provide two [`constraint_setting`](https://docs.bazel.build/versions/master/be/platform.html#constraint_setting)s to act as a standardized namespace for this kind of platform constraint: `@bazel_tools//tools/python:py2_interpreter_path` and `@bazel_tools//tools/python:py3_interpreter_path`. This doc does not mandate any particular structure for the names of [`constraint_value`](https://docs.bazel.build/versions/master/be/platform.html#constraint_value)s associated with these settings. If a platform does not provide a Python 2 runtime, it should have no constraint value associated with `py2_interpreter_path`, and similarly for Python 3.
+
+`PyRuntimeInfo` has the following fields, each of which corresponds to an attribute on `py_runtime`. (The last one, `python_version`, is newly added in this doc.)
+
+* `interpreter_path`: If this is a platform runtime, this field is the absolute filesystem path to the interpreter on the target platform. Otherwise, this is `None`.
+
+* `interpreter`: If this is an in-build runtime, this field is a `File` representing the interpreter. Otherwise, this is `None`.
+
+* `files`: If this is an in-build runtime, this field is a depset of `File`s that need to be added to the runfiles of an executable target that uses this toolchain. The value of `interpreter` need not be included in this field. If this is a platform runtime then this field is `None`.
+
+* `python_version`: Either the string `"PY2"` or `"PY3"`, indicating which version of Python the interpreter referenced by `interpreter_path` or `interpreter` is.
+
+The constructor of `PyRuntimeInfo` takes each of these fields as keyword arguments. The constructor enforces the invariants about which combinations of fields may be `None`. Fields that are not meaningful may be omitted; e.g. when `interpreter_path` is given, `interpreter` and `files` may be omitted instead of passing `None`.
+
+It is not possible to directly specify a system command (e.g. `"python"`) in `interpreter_path`. However, this can be done indirectly by creating a wrapper script that invokes the system command, and referencing that script from the `interpreter` field.
+
+Finally, we define a standard Python toolchain rule implementing the new toolchain type. The rule's name is `py_runtime_pair` and it can be loaded from `@bazel_tools//tools/python:toolchain.bzl`. It has two label-valued attributes, `py2_runtime` and `py3_runtime`, that refer to `py_runtime` targets.
+
+### Changes to the native Python rules
+
+The executable Python rules [`py_binary`](https://docs.bazel.build/versions/master/be/python.html#py_binary) and [`py_test`](https://docs.bazel.build/versions/master/be/python.html#py_test) are modified to require the new toolchain type. The Python runtime information is obtained by retrieving a `PyRuntimeInfo` from either the `py2_runtime` or `py3_runtime` field of the toolchain, rather than from `--python_top`. The `python_version` field of the `PyRuntimeInfo` is also checked to ensure that a `py_runtime` didn't accidentally end up in the wrong place.
+
+Since `--python_top` is no longer read, it is deprecated. Since `--python_path` was only read when no runtime information is available, but the toolchain must always be present, it too is deprecated.
+
+Implementation wise, the native `PyRuntimeProvider` is turned into the user-visible `PyRuntimeInfo` by adding Starlark API annotations in the usual way (`@SkylarkCallable`, etc.). A previous version of this proposal suggested defining `PyRuntimeInfo` in Starlark underneath `@bazel_tools` and accessing it from the native rules, but this is technically difficult to implement.
+
+A `python_version` attribute is added to `py_runtime`. It is mandatory and accepts values `"PY2"` and `"PY3"` only.
+
+As a drive-by cleanup (and non-breaking change), the `files` attribute of `py_runtime` is made optional. For the non-hermetic case, specifying `files` is nonsensical and it is even an error to give it a non-empty value. For the hermetic case, `files` can be useful but is by no means necessary if the interpreter requires no additional in-repo inputs (such as when the "interpreter" is just a wrapper script that dispatches to the platform's system interpreter).
+
+### Default toolchain
+
+For convenience, we supply a predefined [toolchain](https://docs.bazel.build/versions/master/be/platform.html#toolchain) of last resort, `@bazel_tools//tools/python:autodetecting_python_toolchain`. This toolchain is registered with lower priority than any user-registered Python toolchain. It simply dispatches to a wrapper script that tries to locate a suitable interpreter from `PATH` at runtime, on a best-effort basis. It has no platform constraints.
+
+## Example
+
+Here is a minimal example that defines a platform whose Python interpreters are located under a non-standard path. The example also defines a Python toolchain to accompany this platform.
+
+```python
+# //platform_defs:BUILD
+
+load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
+
+# Constraint values that represent that the system's "python2" and "python3"
+# executables are located under /usr/weirdpath.
+
+constraint_value(
+ name = "usr_weirdpath_python2",
+ constraint_setting = "@bazel_tools//tools/python:py2_interpreter_path",
+)
+
+constraint_value(
+ name = "usr_weirdpath_python3",
+ constraint_setting = "@bazel_tools//tools/python:py3_interpreter_path",
+)
+
+# A definition of a platform whose Python interpreters are under these paths.
+
+platform(
+ name = "my_platform",
+ constraint_values = [
+ ":usr_weirdpath_python2",
+ ":usr_weirdpath_python3",
+ ],
+)
+
+# Python runtime definitions that reify these system paths as BUILD targets.
+
+py_runtime(
+ name = "my_platform_py2_runtime",
+ interpreter_path = "/usr/weirdpath/python2",
+)
+
+py_runtime(
+ name = "my_platform_py3_runtime",
+ interpreter_path = "/usr/weirdpath/python3",
+)
+
+py_runtime_pair(
+ name = "my_platform_runtimes",
+ py2_runtime = ":my_platform_py2_runtime",
+ py3_runtime = ":my_platform_py3_runtime",
+)
+
+# A toolchain definition to expose these runtimes to toolchain resolution.
+
+toolchain(
+ name = "my_platform_python_toolchain",
+ # Since the Python interpreter is invoked at runtime on the target
+ # platform, there's no need to specify execution platform constraints here.
+ target_compatible_with = [
+ # Make sure this toolchain is only selected for a target platform that
+ # advertises that it has interpreters available under /usr/weirdpath.
+ ":usr_weirdpath_python2",
+ ":usr_weirdpath_python3",
+ ],
+ toolchain = ":my_platform_runtimes",
+ toolchain_type = "@bazel_tools//tools/python:toolchain_type",
+)
+```
+
+```python
+# //pkg:BUILD
+
+# An ordinary Python target to build.
+py_binary(
+ name = "my_pybin",
+ srcs = ["my_pybin.py"],
+ python_version = "PY3",
+)
+```
+
+```python
+# WORKSPACE
+
+# Register the custom Python toolchain so it can be chosen for my_platform.
+register_toolchains(
+ "//platform_defs:my_platform_python_toolchain",
+)
+```
+
+We can then build with
+
+```
+bazel build //pkg:my_pybin --platforms=//platform_defs:my_platform
+```
+
+and thanks to toolchain resolution, the resulting executable will automatically know to use the interpreter located at `/usr/weirdpath/python3`.
+
+If we had not defined a custom toolchain, then we'd be stuck with `autodetecting_python_toolchain`, which would fail at execution time if `/usr/weirdpath` were not on `PATH`. (It would also be slightly slower since it requires an extra invocation of the interpreter at execution time to confirm its version.)
+
+## Backward compatibility
+
+The new `@bazel_tools` definitions and the `PyRuntimeInfo` provider are made available immediately. A new flag, `--incompatible_use_python_toolchains`, is created to assist migration. When the flag is enabled, `py_binary` and `py_test` will use the `PyRuntimeInfo` obtained from the toolchain, instead of the one obtained from `--python_top` or the default information in `--python_path`. In addition, when `--incompatible_use_python_toolchains` is enabled it is an error to set the following flags: `--python_top`, `--python_path`, `--python2_path`, `--python3_path`. (The latter two were already deprecated.) These flags will be deleted when the incompatible flag is removed.
+
+Because of how the toolchain framework is implemented, it is not possible to gate whether a rule requires a toolchain type based on a flag. Therefore `py_binary` and `py_test` are made to require `@bazel_tools//tools/python:toolchain_type` immediately and unconditionally. This may impact how toolchain resolution determines the toolchains and execution platforms for a given build, but should not otherwise cause problems so long as the build uses constraints correctly.
+
+The new `python_version` attribute is added to `py_runtime` immediately. Its default value is the same as the `python_version` attribute for `py_binary`, i.e. `PY3` if `--incompatible_py3_is_default` is true and `PY2` otherwise. When `--incompatible_use_python_toolchains` is enabled this attribute becomes mandatory.
+
+## FAQ
+
+#### How can I force a `py_binary` to use a given runtime, say for a particular minor version of Python?
+
+This is not directly addressed by this doc. Note that such a system could be used not just for controlling the minor version of the interpreter, but also to choose between different Python implementations (CPython vs PyPy), compilation modes (optimized, debug), an interpreter linked with a pre-selected set of extensions, etc.
+
+There are two possible designs.
+
+The first design is to put this information in the configuration, and have the toolchain read the configuration to decide which `PyRuntimeInfo` to return. We'd use Starlark Build Configurations to define a flag to represent the Python minor version, and transition the `py_binary` target's configuration to use this version. This configuration would be inherited by the resolved toolchain just like any other dependency inherits its parents configuration. The toolchain could then use a `select()` on the minor version flag to choose which `py_runtime` to depend on.
+
+There's one problem: Currently all toolchains are analyzed in the host configuration. It is expected that this will be addressed soon.
+
+We could even migrate the Python major version to use this approach. Instead of having two different `ToolchainInfo` fields, `py2_runtime` and `py3_runtime`, we'd have a single `py_runtime` field that would be populated with one or the other based on the configuration. (It's still a good idea to keep them as separate attributes in the user-facing toolchain rule, i.e. `py_runtime_pair`, because it's a very common use case to require both major versions of Python in a build. But note that this causes both runtimes to be analyzed as dependencies, even if the whole build uses only one or the other.)
+
+The second design for controlling what runtime is chosen is to introduce additional constraints on the toolchain, and let toolchain resolution solve the problem. However, currently toolchains only support constraints on the target and execution platforms, and this is not a platform-related constraint. What would be needed is a per-target semantic-level constraint system.
+
+The second approach has the advantage of allowing individual runtimes to be registered independently, without having to combine them into a massive `select()`. But the first approach is much more feasible to implement in the short-term.
+
+#### Why `py_runtime_pair` as opposed to some other way of organizing multiple Python runtimes?
+
+Alternatives might include a dictionary mapping from version identifiers to runtimes, or a list of runtimes paired with additional metadata.
+
+The `PY2`/`PY3` dichotomy is already baked into the Python rule set and indeed the Python ecosystem at large. Keeping this concept in the toolchain rule serves to complement, rather than complicate, Bazel's existing Python support.
+
+It will always be possible to add new toolchains, first by extending the schema of the `ToolchainInfo` accepted by the Python rules, and then by defining new user-facing toolchain rules that serve as front-ends for this provider.
+
+#### Why not split Python 2 and Python 3 into two separate toolchain types?
+
+The general pattern for rule sets seems to be to have a single toolchain type representing all of a language's concerns. Case in point: The naming convention for toolchain types is to literally name the target "toolchain_type", and let the package path distinguish its label.
+
+If the way of categorizing Python runtimes changes in the future, it will probably be easier to migrate rules to use a new provider schema than to use a new set of toolchain types.
+
+#### How does the introduction of new symbols to `@bazel_tools` affect the eventual plan to migrate the Python rules to `bazelbuild/rules_python`?
+
+The new `PyRuntimeInfo` provider and `py_runtime_pair` rule would have forwarding aliases set up, so they could be accessed both from `@bazel_tools` and `rules_python` during a future migration window.
+
+Forwarding aliases would also be defined for the toolchain type and the two `constraint_setting`s. Note that aliasing `toolchain_type`s is currently broken ([#7404](https://github.com/bazelbuild/bazel/issues/7404)).
+
+In the initial implementation of this proposal, the predefined `autodetecting_python_toolchain` will be automatically registered in the user's workspace by Bazel. This follows precedent for other languages with built-in support in Bazel. Once the rules are migrated to `rules_python`, registration will not be automatic; the user will have to explicitly call a configuration helper defined in `rules_python` from their own `WORKSPACE` file.
+
+## Changelog
+
+Date | Change
+------------ | ------
+2019-02-12 | Initial version
+2019-02-14 | Make `PyRuntimeInfo` natively defined
+2019-02-15 | Clarify platform runtime vs in-build runtime
+2019-02-21 | Formal approval
diff --git a/proposals/README.md b/proposals/README.md
new file mode 100644
index 0000000..36a8a0b
--- /dev/null
+++ b/proposals/README.md
@@ -0,0 +1,11 @@
+# Python Rules Proposals
+
+This is an index of all design documents and proposals for Python rules, both in native code (the Bazel binary) and in Starlark (the rules_python repository). Some of these proposals are also hosted in this directory.
+
+Proposals that impact native code are also indexed by [bazelbuild/proposals](https://github.com/bazelbuild/proposals), and subject to the [Bazel design process](https://bazel.build/designs/index.html).
+
+Last updated | Status | Title | Author(s)
+------------ | ------------- | ------| ---------
+2019-02-21 | Accepted | [Design for a Python Toolchain](https://github.com/bazelbuild/rules_python/blob/master/proposals/2019-02-12-design-for-a-python-toolchain.md) | [brandjon@](https://github.com/brandjon)
+2018-11-09 | Draft | [Customizing the Python Stub Template](https://github.com/bazelbuild/rules_python/blob/master/proposals/2018-11-08-customizing-the-python-stub-template.md) | [brandjon@](https://github.com/brandjon)
+2019-01-11 | Accepted | [Selecting Between Python 2 and 3](https://github.com/bazelbuild/rules_python/blob/master/proposals/2018-10-25-selecting-between-python-2-and-3.md) | [brandjon@](https://github.com/brandjon)
diff --git a/python/BUILD.bazel b/python/BUILD.bazel
new file mode 100644
index 0000000..c5f2580
--- /dev/null
+++ b/python/BUILD.bazel
@@ -0,0 +1,241 @@
+# Copyright 2017 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.
+"""This package contains two sets of rules:
+
+ 1) the "core" Python rules, which were historically bundled with Bazel and
+ are now either re-exported or copied into this repository; and
+
+ 2) the packaging rules, which were historically simply known as
+ rules_python.
+
+In an ideal renaming, we'd move the packaging rules to a different package so
+that @rules_python//python is only concerned with the core rules.
+"""
+
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load(":current_py_toolchain.bzl", "current_py_toolchain")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]) + [
+ "//python/cc:distribution",
+ "//python/config_settings:distribution",
+ "//python/constraints:distribution",
+ "//python/private:distribution",
+ "//python/runfiles:distribution",
+ ],
+ visibility = ["//:__pkg__"],
+)
+
+# ========= bzl_library targets end =========
+
+bzl_library(
+ name = "current_py_toolchain_bzl",
+ srcs = ["current_py_toolchain.bzl"],
+)
+
+bzl_library(
+ name = "defs_bzl",
+ srcs = [
+ "defs.bzl",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ ":current_py_toolchain_bzl",
+ ":py_binary_bzl",
+ ":py_import_bzl",
+ ":py_info_bzl",
+ ":py_library_bzl",
+ ":py_runtime_bzl",
+ ":py_runtime_info_bzl",
+ ":py_runtime_pair_bzl",
+ ":py_test_bzl",
+ "//python/private:bazel_tools_bzl",
+ ],
+)
+
+bzl_library(
+ name = "proto_bzl",
+ srcs = [
+ "proto.bzl",
+ ],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//python/private/proto:py_proto_library_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_binary_bzl",
+ srcs = ["py_binary.bzl"],
+ deps = ["//python/private:util_bzl"],
+)
+
+bzl_library(
+ name = "py_cc_link_params_info_bzl",
+ srcs = ["py_cc_link_params_info.bzl"],
+)
+
+bzl_library(
+ name = "py_import_bzl",
+ srcs = ["py_import.bzl"],
+ deps = [":py_info_bzl"],
+)
+
+bzl_library(
+ name = "py_info_bzl",
+ srcs = ["py_info.bzl"],
+ deps = ["//python/private:reexports_bzl"],
+)
+
+bzl_library(
+ name = "py_library_bzl",
+ srcs = ["py_library.bzl"],
+ deps = ["//python/private:util_bzl"],
+)
+
+bzl_library(
+ name = "py_runtime_bzl",
+ srcs = ["py_runtime.bzl"],
+ deps = ["//python/private:util_bzl"],
+)
+
+bzl_library(
+ name = "py_runtime_pair_bzl",
+ srcs = ["py_runtime_pair.bzl"],
+ deps = ["//python/private:bazel_tools_bzl"],
+)
+
+bzl_library(
+ name = "py_runtime_info_bzl",
+ srcs = ["py_runtime_info.bzl"],
+ deps = ["//python/private:reexports_bzl"],
+)
+
+bzl_library(
+ name = "py_test_bzl",
+ srcs = ["py_test.bzl"],
+ deps = ["//python/private:util_bzl"],
+)
+
+# NOTE: Remember to add bzl_library targets to //tests:bzl_libraries
+# ========= bzl_library targets end =========
+
+# Filegroup of bzl files that can be used by downstream rules for documentation generation
+filegroup(
+ name = "bzl",
+ srcs = [
+ "defs.bzl",
+ "packaging.bzl",
+ "pip.bzl",
+ "repositories.bzl",
+ "versions.bzl",
+ "//python/pip_install:bzl",
+ "//python/private:bzl",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+# ========= Core rules =========
+
+exports_files([
+ "defs.bzl",
+ "python.bzl", # Deprecated, please use defs.bzl
+])
+
+# This target can be used to inspect the current Python major version. To use,
+# put it in the `flag_values` attribute of a `config_setting` and test it
+# against the values "PY2" or "PY3". It will always match one or the other.
+#
+# If you do not need to test any other flags in combination with the Python
+# version, then as a convenience you may use the predefined `config_setting`s
+# `@rules_python//python:PY2` and `@rules_python//python:PY3`.
+#
+# Example usage:
+#
+# config_setting(
+# name = "py3_on_arm",
+# values = {"cpu": "arm"},
+# flag_values = {"@rules_python//python:python_version": "PY3"},
+# )
+#
+# my_target(
+# ...
+# some_attr = select({
+# ":py3_on_arm": ...,
+# ...
+# }),
+# ...
+# )
+#
+# Caution: Do not `select()` on the built-in command-line flags `--force_python`
+# or `--python_version`, as they do not always reflect the true Python version
+# of the current target. `select()`-ing on them can lead to action conflicts and
+# will be disallowed.
+alias(
+ name = "python_version",
+ actual = "@bazel_tools//tools/python:python_version",
+)
+
+alias(
+ name = "PY2",
+ actual = "@bazel_tools//tools/python:PY2",
+)
+
+alias(
+ name = "PY3",
+ actual = "@bazel_tools//tools/python:PY3",
+)
+
+# The toolchain type for Python rules. Provides a Python 2 and/or Python 3
+# runtime.
+alias(
+ name = "toolchain_type",
+ actual = "@bazel_tools//tools/python:toolchain_type",
+)
+
+# Definitions for a Python toolchain that, at execution time, attempts to detect
+# a platform runtime having the appropriate major Python version. Consider this
+# a toolchain of last resort.
+#
+# The non-strict version allows using a Python 2 interpreter for PY3 targets,
+# and vice versa. The only reason to use this is if you're working around
+# spurious failures due to PY2 vs PY3 validation. Even then, using this is only
+# safe if you know for a fact that your build is completely compatible with the
+# version of the `python` command installed on the target platform.
+
+alias(
+ name = "autodetecting_toolchain",
+ actual = "@bazel_tools//tools/python:autodetecting_toolchain",
+)
+
+alias(
+ name = "autodetecting_toolchain_nonstrict",
+ actual = "@bazel_tools//tools/python:autodetecting_toolchain_nonstrict",
+)
+
+# ========= Packaging rules =========
+
+exports_files([
+ "packaging.bzl",
+ "pip.bzl",
+])
+
+current_py_toolchain(
+ name = "current_py_toolchain",
+)
diff --git a/python/cc/BUILD.bazel b/python/cc/BUILD.bazel
new file mode 100644
index 0000000..0d90e15
--- /dev/null
+++ b/python/cc/BUILD.bazel
@@ -0,0 +1,44 @@
+# Package for C/C++ specific functionality of the Python rules.
+
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
+load("//python/private:current_py_cc_headers.bzl", "current_py_cc_headers")
+
+package(
+ default_visibility = ["//:__subpackages__"],
+)
+
+# This target provides the C headers for whatever the current toolchain is
+# for the consuming rule. It basically acts like a cc_library by forwarding
+# on the providers for the underlying cc_library that the toolchain is using.
+current_py_cc_headers(
+ name = "current_py_cc_headers",
+ # Building this directly will fail unless a py cc toolchain is registered,
+ # and it's only under bzlmod that one is registered by default.
+ tags = [] if BZLMOD_ENABLED else ["manual"],
+ visibility = ["//visibility:public"],
+)
+
+toolchain_type(
+ name = "toolchain_type",
+ visibility = ["//visibility:public"],
+)
+
+bzl_library(
+ name = "py_cc_toolchain_bzl",
+ srcs = ["py_cc_toolchain.bzl"],
+ visibility = ["//visibility:public"],
+ deps = ["//python/private:py_cc_toolchain_bzl"],
+)
+
+bzl_library(
+ name = "py_cc_toolchain_info_bzl",
+ srcs = ["py_cc_toolchain_info.bzl"],
+ visibility = ["//visibility:public"],
+ deps = ["//python/private:py_cc_toolchain_info_bzl"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+)
diff --git a/python/cc/py_cc_toolchain.bzl b/python/cc/py_cc_toolchain.bzl
new file mode 100644
index 0000000..2e782ef
--- /dev/null
+++ b/python/cc/py_cc_toolchain.bzl
@@ -0,0 +1,19 @@
+# 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.
+
+"""Public entry point for py_cc_toolchain rule."""
+
+load("//python/private:py_cc_toolchain_macro.bzl", _py_cc_toolchain = "py_cc_toolchain")
+
+py_cc_toolchain = _py_cc_toolchain
diff --git a/python/cc/py_cc_toolchain_info.bzl b/python/cc/py_cc_toolchain_info.bzl
new file mode 100644
index 0000000..9ea394a
--- /dev/null
+++ b/python/cc/py_cc_toolchain_info.bzl
@@ -0,0 +1,23 @@
+# 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.
+
+"""Provider for C/C++ information about the Python runtime.
+
+NOTE: This is a beta-quality feature. APIs subject to change until
+https://github.com/bazelbuild/rules_python/issues/824 is considered done.
+"""
+
+load("//python/private:py_cc_toolchain_info.bzl", _PyCcToolchainInfo = "PyCcToolchainInfo")
+
+PyCcToolchainInfo = _PyCcToolchainInfo
diff --git a/python/config_settings/BUILD.bazel b/python/config_settings/BUILD.bazel
new file mode 100644
index 0000000..272ba78
--- /dev/null
+++ b/python/config_settings/BUILD.bazel
@@ -0,0 +1,12 @@
+load("//python:versions.bzl", "TOOL_VERSIONS")
+load(":config_settings.bzl", "construct_config_settings")
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["*.bzl"]) + [
+ "BUILD.bazel",
+ ],
+ visibility = ["//python:__pkg__"],
+)
+
+construct_config_settings(python_versions = TOOL_VERSIONS.keys())
diff --git a/python/config_settings/config_settings.bzl b/python/config_settings/config_settings.bzl
new file mode 100644
index 0000000..21e477e
--- /dev/null
+++ b/python/config_settings/config_settings.bzl
@@ -0,0 +1,40 @@
+# 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.
+
+"""This module is used to construct the config settings in the BUILD file in this same package.
+"""
+
+load("@bazel_skylib//rules:common_settings.bzl", "string_flag")
+
+# buildifier: disable=unnamed-macro
+def construct_config_settings(python_versions):
+ """Constructs a set of configs for all Python versions.
+
+ Args:
+ python_versions: The Python versions supported by rules_python.
+ """
+ string_flag(
+ name = "python_version",
+ build_setting_default = python_versions[0],
+ values = python_versions,
+ visibility = ["//visibility:public"],
+ )
+
+ for python_version in python_versions:
+ python_version_constraint_setting = "is_python_" + python_version
+ native.config_setting(
+ name = python_version_constraint_setting,
+ flag_values = {":python_version": python_version},
+ visibility = ["//visibility:public"],
+ )
diff --git a/python/config_settings/private/BUILD.bazel b/python/config_settings/private/BUILD.bazel
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/python/config_settings/private/BUILD.bazel
diff --git a/python/config_settings/private/py_args.bzl b/python/config_settings/private/py_args.bzl
new file mode 100644
index 0000000..09a2646
--- /dev/null
+++ b/python/config_settings/private/py_args.bzl
@@ -0,0 +1,42 @@
+# 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.
+
+"""A helper to extract default args for the transition rule."""
+
+def py_args(name, kwargs):
+ """A helper to extract common py_binary and py_test args
+
+ See https://bazel.build/reference/be/python#py_binary and
+ https://bazel.build/reference/be/python#py_test for the list
+ that should be returned
+
+ Args:
+ name: The name of the target.
+ kwargs: The kwargs to be extracted from; MODIFIED IN-PLACE.
+
+ Returns:
+ A dict with the extracted arguments
+ """
+ return dict(
+ args = kwargs.pop("args", None),
+ data = kwargs.pop("data", None),
+ env = kwargs.pop("env", None),
+ srcs = kwargs.pop("srcs", None),
+ deps = kwargs.pop("deps", None),
+ # See https://bazel.build/reference/be/python#py_binary.main
+ # for default logic.
+ # NOTE: This doesn't match the exact way a regular py_binary searches for
+ # it's main amongst the srcs, but is close enough for most cases.
+ main = kwargs.pop("main", name + ".py"),
+ )
diff --git a/python/config_settings/transition.bzl b/python/config_settings/transition.bzl
new file mode 100644
index 0000000..20e03dc
--- /dev/null
+++ b/python/config_settings/transition.bzl
@@ -0,0 +1,227 @@
+# 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 transition module contains the rule definitions to wrap py_binary and py_test and transition
+them to the desired target platform.
+"""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load("//python:defs.bzl", _py_binary = "py_binary", _py_test = "py_test")
+load("//python/config_settings/private:py_args.bzl", "py_args")
+
+def _transition_python_version_impl(_, attr):
+ return {"//python/config_settings:python_version": str(attr.python_version)}
+
+_transition_python_version = transition(
+ implementation = _transition_python_version_impl,
+ inputs = [],
+ outputs = ["//python/config_settings:python_version"],
+)
+
+def _transition_py_impl(ctx):
+ target = ctx.attr.target
+ windows_constraint = ctx.attr._windows_constraint[platform_common.ConstraintValueInfo]
+ target_is_windows = ctx.target_platform_has_constraint(windows_constraint)
+ executable = ctx.actions.declare_file(ctx.attr.name + (".exe" if target_is_windows else ""))
+ ctx.actions.symlink(
+ is_executable = True,
+ output = executable,
+ target_file = target[DefaultInfo].files_to_run.executable,
+ )
+ zipfile_symlink = None
+ if target_is_windows:
+ # Under Windows, the expected "<name>.zip" does not exist, so we have to
+ # create the symlink ourselves to achieve the same behaviour as in macOS
+ # and Linux.
+ zipfile = None
+ expected_target_path = target[DefaultInfo].files_to_run.executable.short_path[:-4] + ".zip"
+ for file in target[DefaultInfo].default_runfiles.files.to_list():
+ if file.short_path == expected_target_path:
+ zipfile = file
+ zipfile_symlink = ctx.actions.declare_file(ctx.attr.name + ".zip")
+ ctx.actions.symlink(
+ is_executable = True,
+ output = zipfile_symlink,
+ target_file = zipfile,
+ )
+ env = {}
+ for k, v in ctx.attr.env.items():
+ env[k] = ctx.expand_location(v)
+
+ providers = [
+ DefaultInfo(
+ executable = executable,
+ files = depset([zipfile_symlink] if zipfile_symlink else [], transitive = [target[DefaultInfo].files]),
+ runfiles = ctx.runfiles([zipfile_symlink] if zipfile_symlink else []).merge(target[DefaultInfo].default_runfiles),
+ ),
+ target[PyInfo],
+ target[PyRuntimeInfo],
+ # Ensure that the binary we're wrapping is included in code coverage.
+ coverage_common.instrumented_files_info(
+ ctx,
+ dependency_attributes = ["target"],
+ ),
+ target[OutputGroupInfo],
+ # TODO(f0rmiga): testing.TestEnvironment is deprecated in favour of RunEnvironmentInfo but
+ # RunEnvironmentInfo is not exposed in Bazel < 5.3.
+ # https://github.com/bazelbuild/rules_python/issues/901
+ # https://github.com/bazelbuild/bazel/commit/dbdfa07e92f99497be9c14265611ad2920161483
+ testing.TestEnvironment(env),
+ ]
+ return providers
+
+_COMMON_ATTRS = {
+ "deps": attr.label_list(
+ mandatory = False,
+ ),
+ "env": attr.string_dict(
+ mandatory = False,
+ ),
+ "python_version": attr.string(
+ mandatory = True,
+ ),
+ "srcs": attr.label_list(
+ allow_files = True,
+ mandatory = False,
+ ),
+ "target": attr.label(
+ executable = True,
+ cfg = "target",
+ mandatory = True,
+ providers = [PyInfo],
+ ),
+ # "tools" is a hack here. It should be "data" but "data" is not included by default in the
+ # location expansion in the same way it is in the native Python rules. The difference on how
+ # the Bazel deals with those special attributes differ on the LocationExpander, e.g.:
+ # https://github.com/bazelbuild/bazel/blob/ce611646/src/main/java/com/google/devtools/build/lib/analysis/LocationExpander.java#L415-L429
+ #
+ # Since the default LocationExpander used by ctx.expand_location is not the same as the native
+ # rules (it doesn't set "allowDataAttributeEntriesInLabel"), we use "tools" temporarily while a
+ # proper fix in Bazel happens.
+ #
+ # A fix for this was proposed in https://github.com/bazelbuild/bazel/pull/16381.
+ "tools": attr.label_list(
+ allow_files = True,
+ mandatory = False,
+ ),
+ # Required to Opt-in to the transitions feature.
+ "_allowlist_function_transition": attr.label(
+ default = "@bazel_tools//tools/allowlists/function_transition_allowlist",
+ ),
+ "_windows_constraint": attr.label(
+ default = "@platforms//os:windows",
+ ),
+}
+
+_transition_py_binary = rule(
+ _transition_py_impl,
+ attrs = _COMMON_ATTRS,
+ cfg = _transition_python_version,
+ executable = True,
+)
+
+_transition_py_test = rule(
+ _transition_py_impl,
+ attrs = _COMMON_ATTRS,
+ cfg = _transition_python_version,
+ test = True,
+)
+
+def _py_rule(rule_impl, transition_rule, name, python_version, **kwargs):
+ pyargs = py_args(name, kwargs)
+ args = pyargs["args"]
+ data = pyargs["data"]
+ env = pyargs["env"]
+ srcs = pyargs["srcs"]
+ deps = pyargs["deps"]
+ main = pyargs["main"]
+
+ # Attributes common to all build rules.
+ # https://bazel.build/reference/be/common-definitions#common-attributes
+ compatible_with = kwargs.pop("compatible_with", None)
+ deprecation = kwargs.pop("deprecation", None)
+ distribs = kwargs.pop("distribs", None)
+ exec_compatible_with = kwargs.pop("exec_compatible_with", None)
+ exec_properties = kwargs.pop("exec_properties", None)
+ features = kwargs.pop("features", None)
+ restricted_to = kwargs.pop("restricted_to", None)
+ tags = kwargs.pop("tags", None)
+ target_compatible_with = kwargs.pop("target_compatible_with", None)
+ testonly = kwargs.pop("testonly", None)
+ toolchains = kwargs.pop("toolchains", None)
+ visibility = kwargs.pop("visibility", None)
+
+ common_attrs = {
+ "compatible_with": compatible_with,
+ "deprecation": deprecation,
+ "distribs": distribs,
+ "exec_compatible_with": exec_compatible_with,
+ "exec_properties": exec_properties,
+ "features": features,
+ "restricted_to": restricted_to,
+ "target_compatible_with": target_compatible_with,
+ "testonly": testonly,
+ "toolchains": toolchains,
+ }
+
+ # Test-specific extra attributes.
+ if "env_inherit" in kwargs:
+ common_attrs["env_inherit"] = kwargs.pop("env_inherit")
+ if "size" in kwargs:
+ common_attrs["size"] = kwargs.pop("size")
+ if "timeout" in kwargs:
+ common_attrs["timeout"] = kwargs.pop("timeout")
+ if "flaky" in kwargs:
+ common_attrs["flaky"] = kwargs.pop("flaky")
+ if "shard_count" in kwargs:
+ common_attrs["shard_count"] = kwargs.pop("shard_count")
+ if "local" in kwargs:
+ common_attrs["local"] = kwargs.pop("local")
+
+ # Binary-specific extra attributes.
+ if "output_licenses" in kwargs:
+ common_attrs["output_licenses"] = kwargs.pop("output_licenses")
+
+ rule_impl(
+ name = "_" + name,
+ args = args,
+ data = data,
+ deps = deps,
+ env = env,
+ srcs = srcs,
+ main = main,
+ tags = ["manual"] + (tags if tags else []),
+ visibility = ["//visibility:private"],
+ **dicts.add(common_attrs, kwargs)
+ )
+
+ return transition_rule(
+ name = name,
+ args = args,
+ deps = deps,
+ env = env,
+ python_version = python_version,
+ srcs = srcs,
+ tags = tags,
+ target = ":_" + name,
+ tools = data,
+ visibility = visibility,
+ **common_attrs
+ )
+
+def py_binary(name, python_version, **kwargs):
+ return _py_rule(_py_binary, _transition_py_binary, name, python_version, **kwargs)
+
+def py_test(name, python_version, **kwargs):
+ return _py_rule(_py_test, _transition_py_test, name, python_version, **kwargs)
diff --git a/python/constraints/BUILD.bazel b/python/constraints/BUILD.bazel
new file mode 100644
index 0000000..969c867
--- /dev/null
+++ b/python/constraints/BUILD.bazel
@@ -0,0 +1,37 @@
+# Copyright 2019 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.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//python:__pkg__"],
+)
+
+# A constraint_setting to use for constraints related to the location of the
+# system Python 2 interpreter on a platform.
+alias(
+ name = "py2_interpreter_path",
+ actual = "@bazel_tools//tools/python:py2_interpreter_path",
+)
+
+# A constraint_setting to use for constraints related to the location of the
+# system Python 3 interpreter on a platform.
+alias(
+ name = "py3_interpreter_path",
+ actual = "@bazel_tools//tools/python:py3_interpreter_path",
+)
diff --git a/python/current_py_toolchain.bzl b/python/current_py_toolchain.bzl
new file mode 100644
index 0000000..e3345cb
--- /dev/null
+++ b/python/current_py_toolchain.bzl
@@ -0,0 +1,58 @@
+# 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.
+
+"""Public entry point for current_py_toolchain rule."""
+
+def _current_py_toolchain_impl(ctx):
+ toolchain = ctx.toolchains[ctx.attr._toolchain]
+
+ direct = []
+ transitive = []
+ vars = {}
+
+ if toolchain.py3_runtime and toolchain.py3_runtime.interpreter:
+ direct.append(toolchain.py3_runtime.interpreter)
+ transitive.append(toolchain.py3_runtime.files)
+ vars["PYTHON3"] = toolchain.py3_runtime.interpreter.path
+
+ if toolchain.py2_runtime and toolchain.py2_runtime.interpreter:
+ direct.append(toolchain.py2_runtime.interpreter)
+ transitive.append(toolchain.py2_runtime.files)
+ vars["PYTHON2"] = toolchain.py2_runtime.interpreter.path
+
+ files = depset(direct, transitive = transitive)
+ return [
+ toolchain,
+ platform_common.TemplateVariableInfo(vars),
+ DefaultInfo(
+ runfiles = ctx.runfiles(transitive_files = files),
+ files = files,
+ ),
+ ]
+
+current_py_toolchain = rule(
+ doc = """
+ This rule exists so that the current python toolchain can be used in the `toolchains` attribute of
+ other rules, such as genrule. It allows exposing a python toolchain after toolchain resolution has
+ happened, to a rule which expects a concrete implementation of a toolchain, rather than a
+ toolchain_type which could be resolved to that toolchain.
+ """,
+ implementation = _current_py_toolchain_impl,
+ attrs = {
+ "_toolchain": attr.string(default = str(Label("@bazel_tools//tools/python:toolchain_type"))),
+ },
+ toolchains = [
+ str(Label("@bazel_tools//tools/python:toolchain_type")),
+ ],
+)
diff --git a/python/defs.bzl b/python/defs.bzl
new file mode 100644
index 0000000..3fb6b5b
--- /dev/null
+++ b/python/defs.bzl
@@ -0,0 +1,51 @@
+# Copyright 2019 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.
+"""Core rules for building Python projects."""
+
+load("@bazel_tools//tools/python:srcs_version.bzl", _find_requirements = "find_requirements")
+load("//python:py_binary.bzl", _py_binary = "py_binary")
+load("//python:py_info.bzl", internal_PyInfo = "PyInfo")
+load("//python:py_library.bzl", _py_library = "py_library")
+load("//python:py_runtime.bzl", _py_runtime = "py_runtime")
+load("//python:py_runtime_info.bzl", internal_PyRuntimeInfo = "PyRuntimeInfo")
+load("//python:py_runtime_pair.bzl", _py_runtime_pair = "py_runtime_pair")
+load("//python:py_test.bzl", _py_test = "py_test")
+load(":current_py_toolchain.bzl", _current_py_toolchain = "current_py_toolchain")
+load(":py_import.bzl", _py_import = "py_import")
+
+# Patching placeholder: end of loads
+
+# Exports of native-defined providers.
+
+PyInfo = internal_PyInfo
+
+PyRuntimeInfo = internal_PyRuntimeInfo
+
+current_py_toolchain = _current_py_toolchain
+
+py_import = _py_import
+
+# Re-exports of Starlark-defined symbols in @bazel_tools//tools/python.
+
+py_runtime_pair = _py_runtime_pair
+
+find_requirements = _find_requirements
+
+py_library = _py_library
+
+py_binary = _py_binary
+
+py_test = _py_test
+
+py_runtime = _py_runtime
diff --git a/python/extensions/BUILD.bazel b/python/extensions/BUILD.bazel
new file mode 100644
index 0000000..7f6873d
--- /dev/null
+++ b/python/extensions/BUILD.bazel
@@ -0,0 +1,23 @@
+# Copyright 2017 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.
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//extensions:__pkg__"],
+)
diff --git a/python/extensions/pip.bzl b/python/extensions/pip.bzl
new file mode 100644
index 0000000..add69a4
--- /dev/null
+++ b/python/extensions/pip.bzl
@@ -0,0 +1,497 @@
+# 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.
+
+"pip module extension for use with bzlmod"
+
+load("@pythons_hub//:interpreters.bzl", "DEFAULT_PYTHON_VERSION", "INTERPRETER_LABELS")
+load("@rules_python//python:pip.bzl", "whl_library_alias")
+load(
+ "@rules_python//python/pip_install:pip_repository.bzl",
+ "locked_requirements_label",
+ "pip_hub_repository_bzlmod",
+ "pip_repository_attrs",
+ "pip_repository_bzlmod",
+ "use_isolated",
+ "whl_library",
+)
+load("@rules_python//python/pip_install:requirements_parser.bzl", parse_requirements = "parse")
+load("//python/private:normalize_name.bzl", "normalize_name")
+load("//python/private:version_label.bzl", "version_label")
+
+def _whl_mods_impl(mctx):
+ """Implementation of the pip.whl_mods tag class.
+
+ This creates the JSON files used to modify the creation of different wheels.
+"""
+ whl_mods_dict = {}
+ for mod in mctx.modules:
+ for whl_mod_attr in mod.tags.whl_mods:
+ if whl_mod_attr.hub_name not in whl_mods_dict.keys():
+ whl_mods_dict[whl_mod_attr.hub_name] = {whl_mod_attr.whl_name: whl_mod_attr}
+ elif whl_mod_attr.whl_name in whl_mods_dict[whl_mod_attr.hub_name].keys():
+ # We cannot have the same wheel name in the same hub, as we
+ # will create the same JSON file name.
+ fail("""\
+Found same whl_name '{}' in the same hub '{}', please use a different hub_name.""".format(
+ whl_mod_attr.whl_name,
+ whl_mod_attr.hub_name,
+ ))
+ else:
+ whl_mods_dict[whl_mod_attr.hub_name][whl_mod_attr.whl_name] = whl_mod_attr
+
+ for hub_name, whl_maps in whl_mods_dict.items():
+ whl_mods = {}
+
+ # create a struct that we can pass to the _whl_mods_repo rule
+ # to create the different JSON files.
+ for whl_name, mods in whl_maps.items():
+ build_content = mods.additive_build_content
+ if mods.additive_build_content_file != None and mods.additive_build_content != "":
+ fail("""\
+You cannot use both the additive_build_content and additive_build_content_file arguments at the same time.
+""")
+ elif mods.additive_build_content_file != None:
+ build_content = mctx.read(mods.additive_build_content_file)
+
+ whl_mods[whl_name] = json.encode(struct(
+ additive_build_content = build_content,
+ copy_files = mods.copy_files,
+ copy_executables = mods.copy_executables,
+ data = mods.data,
+ data_exclude_glob = mods.data_exclude_glob,
+ srcs_exclude_glob = mods.srcs_exclude_glob,
+ ))
+
+ _whl_mods_repo(
+ name = hub_name,
+ whl_mods = whl_mods,
+ )
+
+def _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, whl_map):
+ python_interpreter_target = pip_attr.python_interpreter_target
+
+ # if we do not have the python_interpreter set in the attributes
+ # we programtically find it.
+ hub_name = pip_attr.hub_name
+ if python_interpreter_target == None:
+ python_name = "python_" + version_label(pip_attr.python_version, sep = "_")
+ if python_name not in INTERPRETER_LABELS.keys():
+ fail((
+ "Unable to find interpreter for pip hub '{hub_name}' for " +
+ "python_version={version}: Make sure a corresponding " +
+ '`python.toolchain(python_version="{version}")` call exists'
+ ).format(
+ hub_name = hub_name,
+ version = pip_attr.python_version,
+ ))
+ python_interpreter_target = INTERPRETER_LABELS[python_name]
+
+ pip_name = "{}_{}".format(
+ hub_name,
+ version_label(pip_attr.python_version),
+ )
+ requrements_lock = locked_requirements_label(module_ctx, pip_attr)
+
+ # Parse the requirements file directly in starlark to get the information
+ # needed for the whl_libary declarations below. This is needed to contain
+ # the pip_repository logic to a single module extension.
+ requirements_lock_content = module_ctx.read(requrements_lock)
+ parse_result = parse_requirements(requirements_lock_content)
+ requirements = parse_result.requirements
+ extra_pip_args = pip_attr.extra_pip_args + parse_result.options
+
+ # Create the repository where users load the `requirement` macro. Under bzlmod
+ # this does not create the install_deps() macro.
+ # TODO: we may not need this repository once we have entry points
+ # supported. For now a user can access this repository and use
+ # the entrypoint functionality.
+ pip_repository_bzlmod(
+ name = pip_name,
+ repo_name = pip_name,
+ requirements_lock = pip_attr.requirements_lock,
+ )
+ if hub_name not in whl_map:
+ whl_map[hub_name] = {}
+
+ whl_modifications = {}
+ if pip_attr.whl_modifications != None:
+ for mod, whl_name in pip_attr.whl_modifications.items():
+ whl_modifications[whl_name] = mod
+
+ # Create a new wheel library for each of the different whls
+ for whl_name, requirement_line in requirements:
+ # We are not using the "sanitized name" because the user
+ # would need to guess what name we modified the whl name
+ # to.
+ annotation = whl_modifications.get(whl_name)
+ whl_name = normalize_name(whl_name)
+ whl_library(
+ name = "%s_%s" % (pip_name, whl_name),
+ requirement = requirement_line,
+ repo = pip_name,
+ repo_prefix = pip_name + "_",
+ annotation = annotation,
+ python_interpreter = pip_attr.python_interpreter,
+ python_interpreter_target = python_interpreter_target,
+ quiet = pip_attr.quiet,
+ timeout = pip_attr.timeout,
+ isolated = use_isolated(module_ctx, pip_attr),
+ extra_pip_args = extra_pip_args,
+ download_only = pip_attr.download_only,
+ pip_data_exclude = pip_attr.pip_data_exclude,
+ enable_implicit_namespace_pkgs = pip_attr.enable_implicit_namespace_pkgs,
+ environment = pip_attr.environment,
+ )
+
+ if whl_name not in whl_map[hub_name]:
+ whl_map[hub_name][whl_name] = {}
+
+ whl_map[hub_name][whl_name][pip_attr.python_version] = pip_name + "_"
+
+def _pip_impl(module_ctx):
+ """Implementation of a class tag that creates the pip hub(s) and corresponding pip spoke, alias and whl repositories.
+
+ This implmentation iterates through all of the `pip.parse` calls and creates
+ different pip hub repositories based on the "hub_name". Each of the
+ pip calls create spoke repos that uses a specific Python interpreter.
+
+ In a MODULES.bazel file we have:
+
+ pip.parse(
+ hub_name = "pip",
+ python_version = 3.9,
+ requirements_lock = "//:requirements_lock_3_9.txt",
+ requirements_windows = "//:requirements_windows_3_9.txt",
+ )
+ pip.parse(
+ hub_name = "pip",
+ python_version = 3.10,
+ requirements_lock = "//:requirements_lock_3_10.txt",
+ requirements_windows = "//:requirements_windows_3_10.txt",
+ )
+
+ For instance, we have a hub with the name of "pip".
+ A repository named the following is created. It is actually called last when
+ all of the pip spokes are collected.
+
+ - @@rules_python~override~pip~pip
+
+ As shown in the example code above we have the following.
+ Two different pip.parse statements exist in MODULE.bazel provide the hub_name "pip".
+ These definitions create two different pip spoke repositories that are
+ related to the hub "pip".
+ One spoke uses Python 3.9 and the other uses Python 3.10. This code automatically
+ determines the Python version and the interpreter.
+ Both of these pip spokes contain requirements files that includes websocket
+ and its dependencies.
+
+ Two different repositories are created for the two spokes:
+
+ - @@rules_python~override~pip~pip_39
+ - @@rules_python~override~pip~pip_310
+
+ The different spoke names are a combination of the hub_name and the Python version.
+ In the future we may remove this repository, but we do not support entry points.
+ yet, and that functionality exists in these repos.
+
+ We also need repositories for the wheels that the different pip spokes contain.
+ For each Python version a different wheel repository is created. In our example
+ each pip spoke had a requirments file that contained websockets. We
+ then create two different wheel repositories that are named the following.
+
+ - @@rules_python~override~pip~pip_39_websockets
+ - @@rules_python~override~pip~pip_310_websockets
+
+ And if the wheel has any other dependies subsequest wheels are created in the same fashion.
+
+ We also create a repository for the wheel alias. We want to just use the syntax
+ 'requirement("websockets")' we need to have an alias repository that is named:
+
+ - @@rules_python~override~pip~pip_websockets
+
+ This repository contains alias statements for the different wheel components (pkg, data, etc).
+ Each of those aliases has a select that resolves to a spoke repository depending on
+ the Python version.
+
+ Also we may have more than one hub as defined in a MODULES.bazel file. So we could have multiple
+ hubs pointing to various different pip spokes.
+
+ Some other business rules notes. A hub can only have one spoke per Python version. We cannot
+ have a hub named "pip" that has two spokes that use the Python 3.9 interpreter. Second
+ we cannot have the same hub name used in submodules. The hub name has to be globally
+ unique.
+
+ This implementation reuses elements of non-bzlmod code and also reuses the first implementation
+ of pip bzlmod, but adds the capability to have multiple pip.parse calls.
+
+ This implementation also handles the creation of whl_modification JSON files that are used
+ during the creation of wheel libraries. These JSON files used via the annotations argument
+ when calling wheel_installer.py.
+
+ Args:
+ module_ctx: module contents
+
+ """
+
+ # Build all of the wheel modifications if the tag class is called.
+ _whl_mods_impl(module_ctx)
+
+ # Used to track all the different pip hubs and the spoke pip Python
+ # versions.
+ pip_hub_map = {}
+
+ # Keeps track of all the hub's whl repos across the different versions.
+ # dict[hub, dict[whl, dict[version, str pip]]]
+ # Where hub, whl, and pip are the repo names
+ hub_whl_map = {}
+
+ for mod in module_ctx.modules:
+ for pip_attr in mod.tags.parse:
+ hub_name = pip_attr.hub_name
+ if hub_name in pip_hub_map:
+ # We cannot have two hubs with the same name in different
+ # modules.
+ if pip_hub_map[hub_name].module_name != mod.name:
+ fail((
+ "Duplicate cross-module pip hub named '{hub}': pip hub " +
+ "names must be unique across modules. First defined " +
+ "by module '{first_module}', second attempted by " +
+ "module '{second_module}'"
+ ).format(
+ hub = hub_name,
+ first_module = pip_hub_map[hub_name].module_name,
+ second_module = mod.name,
+ ))
+
+ if pip_attr.python_version in pip_hub_map[hub_name].python_versions:
+ fail((
+ "Duplicate pip python version '{version}' for hub " +
+ "'{hub}' in module '{module}': the Python versions " +
+ "used for a hub must be unique"
+ ).format(
+ hub = hub_name,
+ module = mod.name,
+ version = pip_attr.python_version,
+ ))
+ else:
+ pip_hub_map[pip_attr.hub_name].python_versions.append(pip_attr.python_version)
+ else:
+ pip_hub_map[pip_attr.hub_name] = struct(
+ module_name = mod.name,
+ python_versions = [pip_attr.python_version],
+ )
+
+ _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, hub_whl_map)
+
+ for hub_name, whl_map in hub_whl_map.items():
+ for whl_name, version_map in whl_map.items():
+ if DEFAULT_PYTHON_VERSION not in version_map:
+ fail((
+ "Default python version '{version}' missing in pip " +
+ "hub '{hub}': update your pip.parse() calls so that " +
+ 'includes `python_version = "{version}"`'
+ ).format(
+ version = DEFAULT_PYTHON_VERSION,
+ hub = hub_name,
+ ))
+
+ # Create the alias repositories which contains different select
+ # statements These select statements point to the different pip
+ # whls that are based on a specific version of Python.
+ whl_library_alias(
+ name = hub_name + "_" + whl_name,
+ wheel_name = whl_name,
+ default_version = DEFAULT_PYTHON_VERSION,
+ version_map = version_map,
+ )
+
+ # Create the hub repository for pip.
+ pip_hub_repository_bzlmod(
+ name = hub_name,
+ repo_name = hub_name,
+ whl_library_alias_names = whl_map.keys(),
+ )
+
+def _pip_parse_ext_attrs():
+ attrs = dict({
+ "hub_name": attr.string(
+ mandatory = True,
+ doc = """
+The name of the repo pip dependencies will be accessible from.
+
+This name must be unique between modules; unless your module is guaranteed to
+always be the root module, it's highly recommended to include your module name
+in the hub name. Repo mapping, `use_repo(..., pip="my_modules_pip_deps")`, can
+be used for shorter local names within your module.
+
+Within a module, the same `hub_name` can be specified to group different Python
+versions of pip dependencies under one repository name. This allows using a
+Python version-agnostic name when referring to pip dependencies; the
+correct version will be automatically selected.
+
+Typically, a module will only have a single hub of pip dependencies, but this
+is not required. Each hub is a separate resolution of pip dependencies. This
+means if different programs need different versions of some library, separate
+hubs can be created, and each program can use its respective hub's targets.
+Targets from different hubs should not be used together.
+""",
+ ),
+ "python_version": attr.string(
+ default = DEFAULT_PYTHON_VERSION,
+ doc = """
+The Python version to use for resolving the pip dependencies. If not specified,
+then the default Python version (as set by the root module or rules_python)
+will be used.
+
+The version specified here must have a corresponding `python.toolchain()`
+configured. This attribute defaults to the version of the toolchain
+that is set as the default Python version. Or if only one toolchain
+is used, this attribute defaults to that version of Python.
+""",
+ ),
+ "whl_modifications": attr.label_keyed_string_dict(
+ mandatory = False,
+ doc = """\
+A dict of labels to wheel names that is typically generated by the whl_modifications.
+The labels are JSON config files describing the modifications.
+""",
+ ),
+ }, **pip_repository_attrs)
+
+ # Like the pip_repository rule, we end up setting this manually so
+ # don't allow users to override it.
+ attrs.pop("repo_prefix")
+
+ # incompatible_generate_aliases is always True in bzlmod
+ attrs.pop("incompatible_generate_aliases")
+
+ return attrs
+
+def _whl_mod_attrs():
+ attrs = {
+ "additive_build_content": attr.string(
+ doc = "(str, optional): Raw text to add to the generated `BUILD` file of a package.",
+ ),
+ "additive_build_content_file": attr.label(
+ doc = """\
+(label, optional): path to a BUILD file to add to the generated
+`BUILD` file of a package. You cannot use both additive_build_content and additive_build_content_file
+arguments at the same time.""",
+ ),
+ "copy_executables": attr.string_dict(
+ doc = """\
+(dict, optional): A mapping of `src` and `out` files for
+[@bazel_skylib//rules:copy_file.bzl][cf]. Targets generated here will also be flagged as
+executable.""",
+ ),
+ "copy_files": attr.string_dict(
+ doc = """\
+(dict, optional): A mapping of `src` and `out` files for
+[@bazel_skylib//rules:copy_file.bzl][cf]""",
+ ),
+ "data": attr.string_list(
+ doc = """\
+(list, optional): A list of labels to add as `data` dependencies to
+the generated `py_library` target.""",
+ ),
+ "data_exclude_glob": attr.string_list(
+ doc = """\
+(list, optional): A list of exclude glob patterns to add as `data` to
+the generated `py_library` target.""",
+ ),
+ "hub_name": attr.string(
+ doc = """\
+Name of the whl modification, hub we use this name to set the modifications for
+pip.parse. If you have different pip hubs you can use a different name,
+otherwise it is best practice to just use one.
+
+You cannot have the same `hub_name` in different modules. You can reuse the same
+name in the same module for different wheels that you put in the same hub, but you
+cannot have a child module that uses the same `hub_name`.
+""",
+ mandatory = True,
+ ),
+ "srcs_exclude_glob": attr.string_list(
+ doc = """\
+(list, optional): A list of labels to add as `srcs` to the generated
+`py_library` target.""",
+ ),
+ "whl_name": attr.string(
+ doc = "The whl name that the modifications are used for.",
+ mandatory = True,
+ ),
+ }
+ return attrs
+
+pip = module_extension(
+ doc = """\
+This extension is used to make dependencies from pip available.
+
+pip.parse:
+To use, call `pip.parse()` and specify `hub_name` and your requirements file.
+Dependencies will be downloaded and made available in a repo named after the
+`hub_name` argument.
+
+Each `pip.parse()` call configures a particular Python version. Multiple calls
+can be made to configure different Python versions, and will be grouped by
+the `hub_name` argument. This allows the same logical name, e.g. `@pip//numpy`
+to automatically resolve to different, Python version-specific, libraries.
+
+pip.whl_mods:
+This tag class is used to help create JSON files to describe modifications to
+the BUILD files for wheels.
+""",
+ implementation = _pip_impl,
+ tag_classes = {
+ "parse": tag_class(
+ attrs = _pip_parse_ext_attrs(),
+ doc = """\
+This tag class is used to create a pip hub and all of the spokes that are part of that hub.
+This tag class reuses most of the pip attributes that are found in
+@rules_python//python/pip_install:pip_repository.bzl.
+The exceptions are it does not use the args 'repo_prefix',
+and 'incompatible_generate_aliases'. We set the repository prefix
+for the user and the alias arg is always True in bzlmod.
+""",
+ ),
+ "whl_mods": tag_class(
+ attrs = _whl_mod_attrs(),
+ doc = """\
+This tag class is used to create JSON file that are used when calling wheel_builder.py. These
+JSON files contain instructions on how to modify a wheel's project. Each of the attributes
+create different modifications based on the type of attribute. Previously to bzlmod these
+JSON files where referred to as annotations, and were renamed to whl_modifications in this
+extension.
+""",
+ ),
+ },
+)
+
+def _whl_mods_repo_impl(rctx):
+ rctx.file("BUILD.bazel", "")
+ for whl_name, mods in rctx.attr.whl_mods.items():
+ rctx.file("{}.json".format(whl_name), mods)
+
+_whl_mods_repo = repository_rule(
+ doc = """\
+This rule creates json files based on the whl_mods attribute.
+""",
+ implementation = _whl_mods_repo_impl,
+ attrs = {
+ "whl_mods": attr.string_dict(
+ mandatory = True,
+ doc = "JSON endcoded string that is provided to wheel_builder.py",
+ ),
+ },
+)
diff --git a/python/extensions/private/BUILD.bazel b/python/extensions/private/BUILD.bazel
new file mode 100644
index 0000000..f367b71
--- /dev/null
+++ b/python/extensions/private/BUILD.bazel
@@ -0,0 +1,23 @@
+# Copyright 2022 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.
+
+package(default_visibility = ["//visibility:private"])
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//python/extensions/private:__pkg__"],
+)
diff --git a/python/extensions/private/internal_deps.bzl b/python/extensions/private/internal_deps.bzl
new file mode 100644
index 0000000..27e290c
--- /dev/null
+++ b/python/extensions/private/internal_deps.bzl
@@ -0,0 +1,23 @@
+# 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.
+
+"Python toolchain module extension for internal rule use"
+
+load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies")
+
+# buildifier: disable=unused-variable
+def _internal_deps_impl(module_ctx):
+ pip_install_dependencies()
+
+internal_deps = module_extension(
+ doc = "This extension to register internal rules_python dependecies.",
+ implementation = _internal_deps_impl,
+ tag_classes = {
+ "install": tag_class(attrs = dict()),
+ },
+)
diff --git a/python/extensions/private/pythons_hub.bzl b/python/extensions/private/pythons_hub.bzl
new file mode 100644
index 0000000..a64f203
--- /dev/null
+++ b/python/extensions/private/pythons_hub.bzl
@@ -0,0 +1,142 @@
+# 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.
+
+"Repo rule used by bzlmod extension to create a repo that has a map of Python interpreters and their labels"
+
+load("//python:versions.bzl", "MINOR_MAPPING", "WINDOWS_NAME")
+load(
+ "//python/private:toolchains_repo.bzl",
+ "get_host_os_arch",
+ "get_host_platform",
+ "get_repository_name",
+ "python_toolchain_build_file_content",
+)
+
+def _have_same_length(*lists):
+ if not lists:
+ fail("expected at least one list")
+ return len({len(length): None for length in lists}) == 1
+
+def _get_version(python_version):
+ # we need to get the MINOR_MAPPING or use the full version
+ if python_version in MINOR_MAPPING:
+ python_version = MINOR_MAPPING[python_version]
+ return python_version
+
+def _python_toolchain_build_file_content(
+ prefixes,
+ python_versions,
+ set_python_version_constraints,
+ user_repository_names,
+ workspace_location):
+ """This macro iterates over each of the lists and returns the toolchain content.
+
+ python_toolchain_build_file_content is called to generate each of the toolchain
+ definitions.
+ """
+
+ if not _have_same_length(python_versions, set_python_version_constraints, user_repository_names):
+ fail("all lists must have the same length")
+
+ rules_python = get_repository_name(workspace_location)
+
+ # Iterate over the length of python_versions and call
+ # build the toolchain content by calling python_toolchain_build_file_content
+ return "\n".join([python_toolchain_build_file_content(
+ prefix = prefixes[i],
+ python_version = _get_version(python_versions[i]),
+ set_python_version_constraint = set_python_version_constraints[i],
+ user_repository_name = user_repository_names[i],
+ rules_python = rules_python,
+ ) for i in range(len(python_versions))])
+
+_build_file_for_hub_template = """
+INTERPRETER_LABELS = {{
+{interpreter_labels}
+}}
+DEFAULT_PYTHON_VERSION = "{default_python_version}"
+"""
+
+_line_for_hub_template = """\
+ "{name}": Label("@{name}_{platform}//:{path}"),
+"""
+
+def _hub_repo_impl(rctx):
+ # Create the various toolchain definitions and
+ # write them to the BUILD file.
+ rctx.file(
+ "BUILD.bazel",
+ _python_toolchain_build_file_content(
+ rctx.attr.toolchain_prefixes,
+ rctx.attr.toolchain_python_versions,
+ rctx.attr.toolchain_set_python_version_constraints,
+ rctx.attr.toolchain_user_repository_names,
+ rctx.attr._rules_python_workspace,
+ ),
+ executable = False,
+ )
+
+ (os, arch) = get_host_os_arch(rctx)
+ platform = get_host_platform(os, arch)
+ is_windows = (os == WINDOWS_NAME)
+ path = "python.exe" if is_windows else "bin/python3"
+
+ # Create a dict that is later used to create
+ # a symlink to a interpreter.
+ interpreter_labels = "".join([_line_for_hub_template.format(
+ name = name,
+ platform = platform,
+ path = path,
+ ) for name in rctx.attr.toolchain_user_repository_names])
+
+ rctx.file(
+ "interpreters.bzl",
+ _build_file_for_hub_template.format(
+ interpreter_labels = interpreter_labels,
+ default_python_version = rctx.attr.default_python_version,
+ ),
+ executable = False,
+ )
+
+hub_repo = repository_rule(
+ doc = """\
+This private rule create a repo with a BUILD file that contains a map of interpreter names
+and the labels to said interpreters. This map is used to by the interpreter hub extension.
+This rule also writes out the various toolchains for the different Python versions.
+""",
+ implementation = _hub_repo_impl,
+ attrs = {
+ "default_python_version": attr.string(
+ doc = "Default Python version for the build.",
+ mandatory = True,
+ ),
+ "toolchain_prefixes": attr.string_list(
+ doc = "List prefixed for the toolchains",
+ mandatory = True,
+ ),
+ "toolchain_python_versions": attr.string_list(
+ doc = "List of Python versions for the toolchains",
+ mandatory = True,
+ ),
+ "toolchain_set_python_version_constraints": attr.string_list(
+ doc = "List of version contraints for the toolchains",
+ mandatory = True,
+ ),
+ "toolchain_user_repository_names": attr.string_list(
+ doc = "List of the user repo names for the toolchains",
+ mandatory = True,
+ ),
+ "_rules_python_workspace": attr.label(default = Label("//:does_not_matter_what_this_name_is")),
+ },
+)
diff --git a/python/extensions/python.bzl b/python/extensions/python.bzl
new file mode 100644
index 0000000..2d4032a
--- /dev/null
+++ b/python/extensions/python.bzl
@@ -0,0 +1,258 @@
+# 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.
+
+"Python toolchain module extensions for use with bzlmod"
+
+load("//python:repositories.bzl", "python_register_toolchains")
+load("//python/extensions/private:pythons_hub.bzl", "hub_repo")
+load("//python/private:toolchains_repo.bzl", "multi_toolchain_aliases")
+
+# This limit can be increased essentially arbitrarily, but doing so will cause a rebuild of all
+# targets using any of these toolchains due to the changed repository name.
+_MAX_NUM_TOOLCHAINS = 9999
+_TOOLCHAIN_INDEX_PAD_LENGTH = len(str(_MAX_NUM_TOOLCHAINS))
+
+def _toolchain_prefix(index, name):
+ """Prefixes the given name with the index, padded with zeros to ensure lexicographic sorting.
+
+ Examples:
+ _toolchain_prefix( 2, "foo") == "_0002_foo_"
+ _toolchain_prefix(2000, "foo") == "_2000_foo_"
+ """
+ return "_{}_{}_".format(_left_pad_zero(index, _TOOLCHAIN_INDEX_PAD_LENGTH), name)
+
+def _left_pad_zero(index, length):
+ if index < 0:
+ fail("index must be non-negative")
+ return ("0" * length + str(index))[-length:]
+
+# Printing a warning msg not debugging, so we have to disable
+# the buildifier check.
+# buildifier: disable=print
+def _print_warn(msg):
+ print("WARNING:", msg)
+
+def _python_register_toolchains(name, toolchain_attr, version_constraint):
+ """Calls python_register_toolchains and returns a struct used to collect the toolchains.
+ """
+ python_register_toolchains(
+ name = name,
+ python_version = toolchain_attr.python_version,
+ register_coverage_tool = toolchain_attr.configure_coverage_tool,
+ ignore_root_user_error = toolchain_attr.ignore_root_user_error,
+ set_python_version_constraint = version_constraint,
+ )
+ return struct(
+ python_version = toolchain_attr.python_version,
+ set_python_version_constraint = str(version_constraint),
+ name = name,
+ )
+
+def _python_impl(module_ctx):
+ # The toolchain info structs to register, in the order to register them in.
+ toolchains = []
+
+ # We store the default toolchain separately to ensure it is the last
+ # toolchain added to toolchains.
+ default_toolchain = None
+
+ # Map of string Major.Minor to the toolchain name and module name
+ global_toolchain_versions = {}
+
+ for mod in module_ctx.modules:
+ module_toolchain_versions = []
+
+ for toolchain_attr in mod.tags.toolchain:
+ toolchain_version = toolchain_attr.python_version
+ toolchain_name = "python_" + toolchain_version.replace(".", "_")
+
+ # Duplicate versions within a module indicate a misconfigured module.
+ if toolchain_version in module_toolchain_versions:
+ _fail_duplicate_module_toolchain_version(toolchain_version, mod.name)
+ module_toolchain_versions.append(toolchain_version)
+
+ # Ignore version collisions in the global scope because there isn't
+ # much else that can be done. Modules don't know and can't control
+ # what other modules do, so the first in the dependency graph wins.
+ if toolchain_version in global_toolchain_versions:
+ _warn_duplicate_global_toolchain_version(
+ toolchain_version,
+ first = global_toolchain_versions[toolchain_version],
+ second_toolchain_name = toolchain_name,
+ second_module_name = mod.name,
+ )
+ continue
+ global_toolchain_versions[toolchain_version] = struct(
+ toolchain_name = toolchain_name,
+ module_name = mod.name,
+ )
+
+ # Only the root module and rules_python are allowed to specify the default
+ # toolchain for a couple reasons:
+ # * It prevents submodules from specifying different defaults and only
+ # one of them winning.
+ # * rules_python needs to set a soft default in case the root module doesn't,
+ # e.g. if the root module doesn't use Python itself.
+ # * The root module is allowed to override the rules_python default.
+ if mod.is_root:
+ # A single toolchain is treated as the default because it's unambiguous.
+ is_default = toolchain_attr.is_default or len(mod.tags.toolchain) == 1
+ elif mod.name == "rules_python" and not default_toolchain:
+ # We don't do the len() check because we want the default that rules_python
+ # sets to be clearly visible.
+ is_default = toolchain_attr.is_default
+ else:
+ is_default = False
+
+ # We have already found one default toolchain, and we can only have
+ # one.
+ if is_default and default_toolchain != None:
+ _fail_multiple_default_toolchains(
+ first = default_toolchain.name,
+ second = toolchain_name,
+ )
+
+ toolchain_info = _python_register_toolchains(
+ toolchain_name,
+ toolchain_attr,
+ version_constraint = not is_default,
+ )
+
+ if is_default:
+ default_toolchain = toolchain_info
+ else:
+ toolchains.append(toolchain_info)
+
+ # A default toolchain is required so that the non-version-specific rules
+ # are able to match a toolchain.
+ if default_toolchain == None:
+ fail("No default Python toolchain configured. Is rules_python missing `is_default=True`?")
+
+ # The last toolchain in the BUILD file is set as the default
+ # toolchain. We need the default last.
+ toolchains.append(default_toolchain)
+
+ if len(toolchains) > _MAX_NUM_TOOLCHAINS:
+ fail("more than {} python versions are not supported".format(_MAX_NUM_TOOLCHAINS))
+
+ # Create the pythons_hub repo for the interpreter meta data and the
+ # the various toolchains.
+ hub_repo(
+ name = "pythons_hub",
+ default_python_version = default_toolchain.python_version,
+ toolchain_prefixes = [
+ _toolchain_prefix(index, toolchain.name)
+ for index, toolchain in enumerate(toolchains)
+ ],
+ toolchain_python_versions = [t.python_version for t in toolchains],
+ toolchain_set_python_version_constraints = [t.set_python_version_constraint for t in toolchains],
+ toolchain_user_repository_names = [t.name for t in toolchains],
+ )
+
+ # This is require in order to support multiple version py_test
+ # and py_binary
+ multi_toolchain_aliases(
+ name = "python_versions",
+ python_versions = {
+ version: entry.toolchain_name
+ for version, entry in global_toolchain_versions.items()
+ },
+ )
+
+def _fail_duplicate_module_toolchain_version(version, module):
+ fail(("Duplicate module toolchain version: module '{module}' attempted " +
+ "to use version '{version}' multiple times in itself").format(
+ version = version,
+ module = module,
+ ))
+
+def _warn_duplicate_global_toolchain_version(version, first, second_toolchain_name, second_module_name):
+ _print_warn((
+ "Ignoring toolchain '{second_toolchain}' from module '{second_module}': " +
+ "Toolchain '{first_toolchain}' from module '{first_module}' " +
+ "already registered Python version {version} and has precedence"
+ ).format(
+ first_toolchain = first.toolchain_name,
+ first_module = first.module_name,
+ second_module = second_module_name,
+ second_toolchain = second_toolchain_name,
+ version = version,
+ ))
+
+def _fail_multiple_default_toolchains(first, second):
+ fail(("Multiple default toolchains: only one toolchain " +
+ "can have is_default=True. First default " +
+ "was toolchain '{first}'. Second was '{second}'").format(
+ first = first,
+ second = second,
+ ))
+
+python = module_extension(
+ doc = """Bzlmod extension that is used to register Python toolchains.
+""",
+ implementation = _python_impl,
+ tag_classes = {
+ "toolchain": tag_class(
+ doc = """Tag class used to register Python toolchains.
+Use this tag class to register one or more Python toolchains. This class
+is also potentially called by sub modules. The following covers different
+business rules and use cases.
+
+Toolchains in the Root Module
+
+This class registers all toolchains in the root module.
+
+Toolchains in Sub Modules
+
+It will create a toolchain that is in a sub module, if the toolchain
+of the same name does not exist in the root module. The extension stops name
+clashing between toolchains in the root module and toolchains in sub modules.
+You cannot configure more than one toolchain as the default toolchain.
+
+Toolchain set as the default version
+
+This extension will not create a toolchain that exists in a sub module,
+if the sub module toolchain is marked as the default version. If you have
+more than one toolchain in your root module, you need to set one of the
+toolchains as the default version. If there is only one toolchain it
+is set as the default toolchain.
+
+Toolchain repository name
+
+A toolchain's repository name uses the format `python_{major}_{minor}`, e.g.
+`python_3_10`. The `major` and `minor` components are
+`major` and `minor` are the Python version from the `python_version` attribute.
+""",
+ attrs = {
+ "configure_coverage_tool": attr.bool(
+ mandatory = False,
+ doc = "Whether or not to configure the default coverage tool for the toolchains.",
+ ),
+ "ignore_root_user_error": attr.bool(
+ default = False,
+ doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files.",
+ mandatory = False,
+ ),
+ "is_default": attr.bool(
+ mandatory = False,
+ doc = "Whether the toolchain is the default version",
+ ),
+ "python_version": attr.string(
+ mandatory = True,
+ doc = "The Python version, in `major.minor` format, e.g '3.12', to create a toolchain for.",
+ ),
+ },
+ ),
+ },
+)
diff --git a/python/packaging.bzl b/python/packaging.bzl
new file mode 100644
index 0000000..d9b9d02
--- /dev/null
+++ b/python/packaging.bzl
@@ -0,0 +1,182 @@
+# 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.
+
+"""Public API for for building wheels."""
+
+load("//python/private:py_package.bzl", "py_package_lib")
+load("//python/private:py_wheel.bzl", _PyWheelInfo = "PyWheelInfo", _py_wheel = "py_wheel")
+load("//python/private:util.bzl", "copy_propagating_kwargs")
+
+# Re-export as public API
+PyWheelInfo = _PyWheelInfo
+
+py_package = rule(
+ implementation = py_package_lib.implementation,
+ doc = """\
+A rule to select all files in transitive dependencies of deps which
+belong to given set of Python packages.
+
+This rule is intended to be used as data dependency to py_wheel rule.
+""",
+ attrs = py_package_lib.attrs,
+)
+
+# Based on https://github.com/aspect-build/bazel-lib/tree/main/lib/private/copy_to_directory.bzl
+# Avoiding a bazelbuild -> aspect-build dependency :(
+def _py_wheel_dist_impl(ctx):
+ dir = ctx.actions.declare_directory(ctx.attr.out)
+ name_file = ctx.attr.wheel[PyWheelInfo].name_file
+ cmds = [
+ "mkdir -p \"%s\"" % dir.path,
+ """cp "{}" "{}/$(cat "{}")" """.format(ctx.files.wheel[0].path, dir.path, name_file.path),
+ ]
+ ctx.actions.run_shell(
+ inputs = ctx.files.wheel + [name_file],
+ outputs = [dir],
+ command = "\n".join(cmds),
+ mnemonic = "CopyToDirectory",
+ progress_message = "Copying files to directory",
+ use_default_shell_env = True,
+ )
+ return [
+ DefaultInfo(files = depset([dir]), runfiles = ctx.runfiles([dir])),
+ ]
+
+py_wheel_dist = rule(
+ doc = """\
+Prepare a dist/ folder, following Python's packaging standard practice.
+
+See https://packaging.python.org/en/latest/tutorials/packaging-projects/#generating-distribution-archives
+which recommends a dist/ folder containing the wheel file(s), source distributions, etc.
+
+This also has the advantage that stamping information is included in the wheel's filename.
+""",
+ implementation = _py_wheel_dist_impl,
+ attrs = {
+ "out": attr.string(doc = "name of the resulting directory", mandatory = True),
+ "wheel": attr.label(doc = "a [py_wheel rule](/docs/packaging.md#py_wheel_rule)", providers = [PyWheelInfo]),
+ },
+)
+
+def py_wheel(name, twine = None, publish_args = [], **kwargs):
+ """Builds a Python Wheel.
+
+ Wheels are Python distribution format defined in https://www.python.org/dev/peps/pep-0427/.
+
+ This macro packages a set of targets into a single wheel.
+ It wraps the [py_wheel rule](#py_wheel_rule).
+
+ Currently only pure-python wheels are supported.
+
+ Examples:
+
+ ```python
+ # Package some specific py_library targets, without their dependencies
+ py_wheel(
+ name = "minimal_with_py_library",
+ # 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",
+ ],
+ )
+
+ # 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_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"],
+ )
+ ```
+
+ To publish the wheel to Pypi, the twine package is required.
+ rules_python doesn't provide twine itself, see https://github.com/bazelbuild/rules_python/issues/1016
+ However you can install it with pip_parse, just like we do in the WORKSPACE file in rules_python.
+
+ Once you've installed twine, you can pass its label to the `twine` attribute of this macro,
+ to get a "[name].publish" target.
+
+ Example:
+
+ ```python
+ py_wheel(
+ name = "my_wheel",
+ twine = "@publish_deps_twine//:pkg",
+ ...
+ )
+ ```
+
+ Now you can run a command like the following, which publishes to https://test.pypi.org/
+
+ ```sh
+ % TWINE_USERNAME=__token__ TWINE_PASSWORD=pypi-*** \\
+ bazel run --stamp --embed_label=1.2.4 -- \\
+ //path/to:my_wheel.publish --repository testpypi
+ ```
+
+ Args:
+ name: A unique name for this target.
+ twine: A label of the external location of the py_library target for twine
+ publish_args: arguments passed to twine, e.g. ["--repository-url", "https://pypi.my.org/simple/"].
+ These are subject to make var expansion, as with the `args` attribute.
+ Note that you can also pass additional args to the bazel run command as in the example above.
+ **kwargs: other named parameters passed to the underlying [py_wheel rule](#py_wheel_rule)
+ """
+ _dist_target = "{}.dist".format(name)
+ py_wheel_dist(
+ name = _dist_target,
+ wheel = name,
+ out = kwargs.pop("dist_folder", "{}_dist".format(name)),
+ **copy_propagating_kwargs(kwargs)
+ )
+
+ _py_wheel(name = name, **kwargs)
+
+ if twine:
+ if not twine.endswith(":pkg"):
+ fail("twine label should look like @my_twine_repo//:pkg")
+ twine_main = twine.replace(":pkg", ":rules_python_wheel_entry_point_twine.py")
+ twine_args = ["upload"]
+ twine_args.extend(publish_args)
+ twine_args.append("$(rootpath :{})/*".format(_dist_target))
+
+ # TODO: use py_binary from //python:defs.bzl after our stardoc setup is less brittle
+ # buildifier: disable=native-py
+ native.py_binary(
+ name = "{}.publish".format(name),
+ srcs = [twine_main],
+ args = twine_args,
+ data = [_dist_target],
+ imports = ["."],
+ main = twine_main,
+ deps = [twine],
+ visibility = kwargs.get("visibility"),
+ **copy_propagating_kwargs(kwargs)
+ )
+
+py_wheel_rule = _py_wheel
diff --git a/python/pip.bzl b/python/pip.bzl
new file mode 100644
index 0000000..cae1591
--- /dev/null
+++ b/python/pip.bzl
@@ -0,0 +1,367 @@
+# Copyright 2017 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 pip requirements into Bazel."""
+
+load("//python/pip_install:pip_repository.bzl", "pip_repository", _package_annotation = "package_annotation")
+load("//python/pip_install:repositories.bzl", "pip_install_dependencies")
+load("//python/pip_install:requirements.bzl", _compile_pip_requirements = "compile_pip_requirements")
+load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
+load(":versions.bzl", "MINOR_MAPPING")
+
+compile_pip_requirements = _compile_pip_requirements
+package_annotation = _package_annotation
+
+def pip_install(requirements = None, name = "pip", **kwargs):
+ """Accepts a locked/compiled requirements file and installs the dependencies listed within.
+
+ ```python
+ load("@rules_python//python:pip.bzl", "pip_install")
+
+ pip_install(
+ name = "pip_deps",
+ requirements = ":requirements.txt",
+ )
+
+ load("@pip_deps//:requirements.bzl", "install_deps")
+
+ install_deps()
+ ```
+
+ Args:
+ requirements (Label): A 'requirements.txt' pip requirements file.
+ name (str, optional): A unique name for the created external repository (default 'pip').
+ **kwargs (dict): Additional arguments to the [`pip_repository`](./pip_repository.md) repository rule.
+ """
+
+ # buildifier: disable=print
+ print("pip_install is deprecated. Please switch to pip_parse. pip_install will be removed in a future release.")
+ pip_parse(requirements = requirements, name = name, **kwargs)
+
+def pip_parse(requirements = None, requirements_lock = None, name = "pip_parsed_deps", **kwargs):
+ """Accepts a locked/compiled requirements file and installs the dependencies listed within.
+
+ Those dependencies become available in a generated `requirements.bzl` file.
+ You can instead check this `requirements.bzl` file into your repo, see the "vendoring" section below.
+
+ This macro wraps the [`pip_repository`](./pip_repository.md) rule that invokes `pip`.
+ In your WORKSPACE file:
+
+ ```python
+ load("@rules_python//python:pip.bzl", "pip_parse")
+
+ pip_parse(
+ name = "pip_deps",
+ requirements_lock = ":requirements.txt",
+ )
+
+ load("@pip_deps//:requirements.bzl", "install_deps")
+
+ install_deps()
+ ```
+
+ You can then reference installed dependencies from a `BUILD` file with:
+
+ ```python
+ load("@pip_deps//:requirements.bzl", "requirement")
+
+ py_library(
+ name = "bar",
+ ...
+ deps = [
+ "//my/other:dep",
+ requirement("requests"),
+ requirement("numpy"),
+ ],
+ )
+ ```
+
+ In addition to the `requirement` macro, which is used to access the generated `py_library`
+ target generated from a package's wheel, The generated `requirements.bzl` file contains
+ functionality for exposing [entry points][whl_ep] as `py_binary` targets as well.
+
+ [whl_ep]: https://packaging.python.org/specifications/entry-points/
+
+ ```python
+ load("@pip_deps//:requirements.bzl", "entry_point")
+
+ alias(
+ name = "pip-compile",
+ actual = entry_point(
+ pkg = "pip-tools",
+ script = "pip-compile",
+ ),
+ )
+ ```
+
+ Note that for packages whose name and script are the same, only the name of the package
+ is needed when calling the `entry_point` macro.
+
+ ```python
+ load("@pip_deps//:requirements.bzl", "entry_point")
+
+ alias(
+ name = "flake8",
+ actual = entry_point("flake8"),
+ )
+ ```
+
+ ## Vendoring the requirements.bzl file
+
+ In some cases you may not want to generate the requirements.bzl file as a repository rule
+ while Bazel is fetching dependencies. For example, if you produce a reusable Bazel module
+ such as a ruleset, you may want to include the requirements.bzl file rather than make your users
+ install the WORKSPACE setup to generate it.
+ See https://github.com/bazelbuild/rules_python/issues/608
+
+ This is the same workflow as Gazelle, which creates `go_repository` rules with
+ [`update-repos`](https://github.com/bazelbuild/bazel-gazelle#update-repos)
+
+ To do this, use the "write to source file" pattern documented in
+ https://blog.aspect.dev/bazel-can-write-to-the-source-folder
+ to put a copy of the generated requirements.bzl into your project.
+ Then load the requirements.bzl file directly rather than from the generated repository.
+ See the example in rules_python/examples/pip_parse_vendored.
+
+ Args:
+ requirements_lock (Label): A fully resolved 'requirements.txt' pip requirement file
+ containing the transitive set of your dependencies. If this file is passed instead
+ of 'requirements' no resolve will take place and pip_repository will create
+ individual repositories for each of your dependencies so that wheels are
+ fetched/built only for the targets specified by 'build/run/test'.
+ Note that if your lockfile is platform-dependent, you can use the `requirements_[platform]`
+ attributes.
+ requirements (Label): Deprecated. See requirements_lock.
+ name (str, optional): The name of the generated repository. The generated repositories
+ containing each requirement will be of the form `<name>_<requirement-name>`.
+ **kwargs (dict): Additional arguments to the [`pip_repository`](./pip_repository.md) repository rule.
+ """
+ pip_install_dependencies()
+
+ # Temporary compatibility shim.
+ # pip_install was previously document to use requirements while pip_parse was using requirements_lock.
+ # We would prefer everyone move to using requirements_lock, but we maintain a temporary shim.
+ reqs_to_use = requirements_lock if requirements_lock else requirements
+
+ pip_repository(
+ name = name,
+ requirements_lock = reqs_to_use,
+ **kwargs
+ )
+
+def _multi_pip_parse_impl(rctx):
+ rules_python = rctx.attr._rules_python_workspace.workspace_name
+ load_statements = []
+ install_deps_calls = []
+ process_requirements_calls = []
+ for python_version, pypi_repository in rctx.attr.pip_parses.items():
+ sanitized_python_version = python_version.replace(".", "_")
+ load_statement = """\
+load(
+ "@{pypi_repository}//:requirements.bzl",
+ _{sanitized_python_version}_install_deps = "install_deps",
+ _{sanitized_python_version}_all_requirements = "all_requirements",
+)""".format(
+ pypi_repository = pypi_repository,
+ sanitized_python_version = sanitized_python_version,
+ )
+ load_statements.append(load_statement)
+ process_requirements_call = """\
+_process_requirements(
+ pkg_labels = _{sanitized_python_version}_all_requirements,
+ python_version = "{python_version}",
+ repo_prefix = "{pypi_repository}_",
+)""".format(
+ pypi_repository = pypi_repository,
+ python_version = python_version,
+ sanitized_python_version = sanitized_python_version,
+ )
+ process_requirements_calls.append(process_requirements_call)
+ install_deps_call = """ _{sanitized_python_version}_install_deps(**whl_library_kwargs)""".format(
+ sanitized_python_version = sanitized_python_version,
+ )
+ install_deps_calls.append(install_deps_call)
+
+ requirements_bzl = """\
+# Generated by python/pip.bzl
+
+load("@{rules_python}//python:pip.bzl", "whl_library_alias")
+{load_statements}
+
+_wheel_names = []
+_version_map = dict()
+def _process_requirements(pkg_labels, python_version, repo_prefix):
+ for pkg_label in pkg_labels:
+ workspace_name = Label(pkg_label).workspace_name
+ wheel_name = workspace_name[len(repo_prefix):]
+ _wheel_names.append(wheel_name)
+ if not wheel_name in _version_map:
+ _version_map[wheel_name] = dict()
+ _version_map[wheel_name][python_version] = repo_prefix
+
+{process_requirements_calls}
+
+def _clean_name(name):
+ return name.replace("-", "_").replace(".", "_").lower()
+
+def requirement(name):
+ return "@{name}_" + _clean_name(name) + "//:pkg"
+
+def whl_requirement(name):
+ return "@{name}_" + _clean_name(name) + "//:whl"
+
+def data_requirement(name):
+ return "@{name}_" + _clean_name(name) + "//:data"
+
+def dist_info_requirement(name):
+ return "@{name}_" + _clean_name(name) + "//:dist_info"
+
+def entry_point(pkg, script = None):
+ fail("Not implemented yet")
+
+def install_deps(**whl_library_kwargs):
+{install_deps_calls}
+ for wheel_name in _wheel_names:
+ whl_library_alias(
+ name = "{name}_" + wheel_name,
+ wheel_name = wheel_name,
+ default_version = "{default_version}",
+ version_map = _version_map[wheel_name],
+ )
+""".format(
+ name = rctx.attr.name,
+ install_deps_calls = "\n".join(install_deps_calls),
+ load_statements = "\n".join(load_statements),
+ process_requirements_calls = "\n".join(process_requirements_calls),
+ rules_python = rules_python,
+ default_version = rctx.attr.default_version,
+ )
+ rctx.file("requirements.bzl", requirements_bzl)
+ rctx.file("BUILD.bazel", "exports_files(['requirements.bzl'])")
+
+_multi_pip_parse = repository_rule(
+ _multi_pip_parse_impl,
+ attrs = {
+ "default_version": attr.string(),
+ "pip_parses": attr.string_dict(),
+ "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
+ },
+)
+
+def _whl_library_alias_impl(rctx):
+ rules_python = rctx.attr._rules_python_workspace.workspace_name
+ if rctx.attr.default_version not in rctx.attr.version_map:
+ fail(
+ """
+Unable to find '{}' in your version map, you may need to update your requirement files.
+ """.format(rctx.attr.version_map),
+ )
+ default_repo_prefix = rctx.attr.version_map[rctx.attr.default_version]
+ version_map = rctx.attr.version_map.items()
+ build_content = ["# Generated by python/pip.bzl"]
+ for alias_name in ["pkg", "whl", "data", "dist_info"]:
+ build_content.append(_whl_library_render_alias_target(
+ alias_name = alias_name,
+ default_repo_prefix = default_repo_prefix,
+ rules_python = rules_python,
+ version_map = version_map,
+ wheel_name = rctx.attr.wheel_name,
+ ))
+ rctx.file("BUILD.bazel", "\n".join(build_content))
+
+def _whl_library_render_alias_target(
+ alias_name,
+ default_repo_prefix,
+ rules_python,
+ version_map,
+ wheel_name):
+ # The template below adds one @, but under bzlmod, the name
+ # is canonical, so we have to add a second @.
+ if BZLMOD_ENABLED:
+ rules_python = "@" + rules_python
+ alias = ["""\
+alias(
+ name = "{alias_name}",
+ actual = select({{""".format(alias_name = alias_name)]
+ for [python_version, repo_prefix] in version_map:
+ alias.append("""\
+ "@{rules_python}//python/config_settings:is_python_{full_python_version}": "{actual}",""".format(
+ full_python_version = MINOR_MAPPING[python_version] if python_version in MINOR_MAPPING else python_version,
+ actual = "@{repo_prefix}{wheel_name}//:{alias_name}".format(
+ repo_prefix = repo_prefix,
+ wheel_name = wheel_name,
+ alias_name = alias_name,
+ ),
+ rules_python = rules_python,
+ ))
+ alias.append("""\
+ "//conditions:default": "{default_actual}",
+ }}),
+ visibility = ["//visibility:public"],
+)""".format(
+ default_actual = "@{repo_prefix}{wheel_name}//:{alias_name}".format(
+ repo_prefix = default_repo_prefix,
+ wheel_name = wheel_name,
+ alias_name = alias_name,
+ ),
+ ))
+ return "\n".join(alias)
+
+whl_library_alias = repository_rule(
+ _whl_library_alias_impl,
+ attrs = {
+ "default_version": attr.string(mandatory = True),
+ "version_map": attr.string_dict(mandatory = True),
+ "wheel_name": attr.string(mandatory = True),
+ "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
+ },
+)
+
+def multi_pip_parse(name, default_version, python_versions, python_interpreter_target, requirements_lock, **kwargs):
+ """NOT INTENDED FOR DIRECT USE!
+
+ This is intended to be used by the multi_pip_parse implementation in the template of the
+ multi_toolchain_aliases repository rule.
+
+ Args:
+ name: the name of the multi_pip_parse repository.
+ default_version: the default Python version.
+ python_versions: all Python toolchain versions currently registered.
+ python_interpreter_target: a dictionary which keys are Python versions and values are resolved host interpreters.
+ requirements_lock: a dictionary which keys are Python versions and values are locked requirements files.
+ **kwargs: extra arguments passed to all wrapped pip_parse.
+
+ Returns:
+ The internal implementation of multi_pip_parse repository rule.
+ """
+ pip_parses = {}
+ for python_version in python_versions:
+ if not python_version in python_interpreter_target:
+ fail("Missing python_interpreter_target for Python version %s in '%s'" % (python_version, name))
+ if not python_version in requirements_lock:
+ fail("Missing requirements_lock for Python version %s in '%s'" % (python_version, name))
+
+ pip_parse_name = name + "_" + python_version.replace(".", "_")
+ pip_parse(
+ name = pip_parse_name,
+ python_interpreter_target = python_interpreter_target[python_version],
+ requirements_lock = requirements_lock[python_version],
+ **kwargs
+ )
+ pip_parses[python_version] = pip_parse_name
+
+ return _multi_pip_parse(
+ name = name,
+ default_version = default_version,
+ pip_parses = pip_parses,
+ )
diff --git a/python/pip_install/.gitignore b/python/pip_install/.gitignore
new file mode 100644
index 0000000..74570cb
--- /dev/null
+++ b/python/pip_install/.gitignore
@@ -0,0 +1,135 @@
+# Intellij
+.ijwb/
+.idea/
+
+# Bazel
+bazel-*
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
diff --git a/python/pip_install/BUILD.bazel b/python/pip_install/BUILD.bazel
new file mode 100644
index 0000000..e8e8633
--- /dev/null
+++ b/python/pip_install/BUILD.bazel
@@ -0,0 +1,34 @@
+filegroup(
+ name = "distribution",
+ srcs = glob(["*.bzl"]) + [
+ "BUILD.bazel",
+ "//python/pip_install/private:distribution",
+ "//python/pip_install/tools/dependency_resolver:distribution",
+ "//python/pip_install/tools/lib:distribution",
+ "//python/pip_install/tools/wheel_installer:distribution",
+ ],
+ visibility = ["//:__pkg__"],
+)
+
+filegroup(
+ name = "bzl",
+ srcs = glob(["*.bzl"]) + [
+ "//python/pip_install/private:bzl_srcs",
+ ],
+ visibility = ["//:__subpackages__"],
+)
+
+filegroup(
+ name = "py_srcs",
+ srcs = [
+ "//python/pip_install/tools/dependency_resolver:py_srcs",
+ "//python/pip_install/tools/lib:py_srcs",
+ "//python/pip_install/tools/wheel_installer:py_srcs",
+ ],
+ visibility = ["//python/pip_install/private:__pkg__"],
+)
+
+exports_files(
+ glob(["*.bzl"]),
+ visibility = ["//docs:__pkg__"],
+)
diff --git a/python/pip_install/pip_hub_repository_requirements_bzlmod.bzl.tmpl b/python/pip_install/pip_hub_repository_requirements_bzlmod.bzl.tmpl
new file mode 100644
index 0000000..4a3d512
--- /dev/null
+++ b/python/pip_install/pip_hub_repository_requirements_bzlmod.bzl.tmpl
@@ -0,0 +1,35 @@
+"""Starlark representation of locked requirements.
+
+@generated by rules_python pip_parse repository rule
+from %%REQUIREMENTS_LOCK%%.
+
+This file is different from the other bzlmod template
+because we do not support entry_point yet.
+"""
+
+all_requirements = %%ALL_REQUIREMENTS%%
+
+all_whl_requirements = %%ALL_WHL_REQUIREMENTS%%
+
+all_data_requirements = %%ALL_DATA_REQUIREMENTS%%
+
+def _clean_name(name):
+ return name.replace("-", "_").replace(".", "_").lower()
+
+def requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "pkg")
+
+def whl_requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "whl")
+
+def data_requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "data")
+
+def dist_info_requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "dist_info")
+
+def entry_point(pkg, script = None):
+ """entry_point returns the target of the canonical label of the package entrypoints.
+ """
+ # TODO: https://github.com/bazelbuild/rules_python/issues/1262
+ print("not implemented")
diff --git a/python/pip_install/pip_repository.bzl b/python/pip_install/pip_repository.bzl
new file mode 100644
index 0000000..99d1fb0
--- /dev/null
+++ b/python/pip_install/pip_repository.bzl
@@ -0,0 +1,769 @@
+# 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.
+
+""
+
+load("//python:repositories.bzl", "get_interpreter_dirname", "is_standalone_interpreter")
+load("//python:versions.bzl", "WINDOWS_NAME")
+load("//python/pip_install:repositories.bzl", "all_requirements")
+load("//python/pip_install:requirements_parser.bzl", parse_requirements = "parse")
+load("//python/pip_install/private:srcs.bzl", "PIP_INSTALL_PY_SRCS")
+load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
+load("//python/private:normalize_name.bzl", "normalize_name")
+load("//python/private:toolchains_repo.bzl", "get_host_os_arch")
+
+CPPFLAGS = "CPPFLAGS"
+
+COMMAND_LINE_TOOLS_PATH_SLUG = "commandlinetools"
+
+def _construct_pypath(rctx):
+ """Helper function to construct a PYTHONPATH.
+
+ Contains entries for code in this repo as well as packages downloaded from //python/pip_install:repositories.bzl.
+ This allows us to run python code inside repository rule implementations.
+
+ Args:
+ rctx: Handle to the repository_context.
+ Returns: String of the PYTHONPATH.
+ """
+
+ # Get the root directory of these rules
+ rules_root = rctx.path(Label("//:BUILD.bazel")).dirname
+ thirdparty_roots = [
+ # Includes all the external dependencies from repositories.bzl
+ rctx.path(Label("@" + repo + "//:BUILD.bazel")).dirname
+ for repo in all_requirements
+ ]
+ separator = ":" if not "windows" in rctx.os.name.lower() else ";"
+ pypath = separator.join([str(p) for p in [rules_root] + thirdparty_roots])
+ return pypath
+
+def _get_python_interpreter_attr(rctx):
+ """A helper function for getting the `python_interpreter` attribute or it's default
+
+ Args:
+ rctx (repository_ctx): Handle to the rule repository context.
+
+ Returns:
+ str: The attribute value or it's default
+ """
+ if rctx.attr.python_interpreter:
+ return rctx.attr.python_interpreter
+
+ if "win" in rctx.os.name:
+ return "python.exe"
+ else:
+ return "python3"
+
+def _resolve_python_interpreter(rctx):
+ """Helper function to find the python interpreter from the common attributes
+
+ Args:
+ rctx: Handle to the rule repository context.
+ Returns: Python interpreter path.
+ """
+ python_interpreter = _get_python_interpreter_attr(rctx)
+
+ if rctx.attr.python_interpreter_target != None:
+ python_interpreter = rctx.path(rctx.attr.python_interpreter_target)
+
+ if BZLMOD_ENABLED:
+ (os, _) = get_host_os_arch(rctx)
+
+ # On Windows, the symlink doesn't work because Windows attempts to find
+ # Python DLLs where the symlink is, not where the symlink points.
+ if os == WINDOWS_NAME:
+ python_interpreter = python_interpreter.realpath
+ elif "/" not in python_interpreter:
+ found_python_interpreter = rctx.which(python_interpreter)
+ if not found_python_interpreter:
+ fail("python interpreter `{}` not found in PATH".format(python_interpreter))
+ python_interpreter = found_python_interpreter
+ return python_interpreter
+
+def _get_xcode_location_cflags(rctx):
+ """Query the xcode sdk location to update cflags
+
+ Figure out if this interpreter target comes from rules_python, and patch the xcode sdk location if so.
+ Pip won't be able to compile c extensions from sdists with the pre built python distributions from indygreg
+ otherwise. See https://github.com/indygreg/python-build-standalone/issues/103
+ """
+
+ # Only run on MacOS hosts
+ if not rctx.os.name.lower().startswith("mac os"):
+ return []
+
+ # Locate xcode-select
+ xcode_select = rctx.which("xcode-select")
+
+ xcode_sdk_location = rctx.execute([xcode_select, "--print-path"])
+ if xcode_sdk_location.return_code != 0:
+ return []
+
+ xcode_root = xcode_sdk_location.stdout.strip()
+ if COMMAND_LINE_TOOLS_PATH_SLUG not in xcode_root.lower():
+ # This is a full xcode installation somewhere like /Applications/Xcode13.0.app/Contents/Developer
+ # so we need to change the path to to the macos specific tools which are in a different relative
+ # path than xcode installed command line tools.
+ xcode_root = "{}/Platforms/MacOSX.platform/Developer".format(xcode_root)
+ return [
+ "-isysroot {}/SDKs/MacOSX.sdk".format(xcode_root),
+ ]
+
+def _get_toolchain_unix_cflags(rctx):
+ """Gather cflags from a standalone toolchain for unix systems.
+
+ Pip won't be able to compile c extensions from sdists with the pre built python distributions from indygreg
+ otherwise. See https://github.com/indygreg/python-build-standalone/issues/103
+ """
+
+ # Only run on Unix systems
+ if not rctx.os.name.lower().startswith(("mac os", "linux")):
+ return []
+
+ # Only update the location when using a standalone toolchain.
+ if not is_standalone_interpreter(rctx, rctx.attr.python_interpreter_target):
+ return []
+
+ er = rctx.execute([
+ rctx.path(rctx.attr.python_interpreter_target).realpath,
+ "-c",
+ "import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}', end='')",
+ ])
+ if er.return_code != 0:
+ fail("could not get python version from interpreter (status {}): {}".format(er.return_code, er.stderr))
+ _python_version = er.stdout
+ include_path = "{}/include/python{}".format(
+ get_interpreter_dirname(rctx, rctx.attr.python_interpreter_target),
+ _python_version,
+ )
+
+ return ["-isystem {}".format(include_path)]
+
+def use_isolated(ctx, attr):
+ """Determine whether or not to pass the pip `--isolated` flag to the pip invocation.
+
+ Args:
+ ctx: repository or module context
+ attr: attributes for the repo rule or tag extension
+
+ Returns:
+ True if --isolated should be passed
+ """
+ use_isolated = attr.isolated
+
+ # The environment variable will take precedence over the attribute
+ isolated_env = ctx.os.environ.get("RULES_PYTHON_PIP_ISOLATED", None)
+ if isolated_env != None:
+ if isolated_env.lower() in ("0", "false"):
+ use_isolated = False
+ else:
+ use_isolated = True
+
+ return use_isolated
+
+def _parse_optional_attrs(rctx, args):
+ """Helper function to parse common attributes of pip_repository and whl_library repository rules.
+
+ This function also serializes the structured arguments as JSON
+ so they can be passed on the command line to subprocesses.
+
+ Args:
+ rctx: Handle to the rule repository context.
+ args: A list of parsed args for the rule.
+ Returns: Augmented args list.
+ """
+
+ if use_isolated(rctx, rctx.attr):
+ args.append("--isolated")
+
+ # Check for None so we use empty default types from our attrs.
+ # Some args want to be list, and some want to be dict.
+ if rctx.attr.extra_pip_args != None:
+ args += [
+ "--extra_pip_args",
+ json.encode(struct(arg = rctx.attr.extra_pip_args)),
+ ]
+
+ if rctx.attr.download_only:
+ args.append("--download_only")
+
+ if rctx.attr.pip_data_exclude != None:
+ args += [
+ "--pip_data_exclude",
+ json.encode(struct(arg = rctx.attr.pip_data_exclude)),
+ ]
+
+ if rctx.attr.enable_implicit_namespace_pkgs:
+ args.append("--enable_implicit_namespace_pkgs")
+
+ if rctx.attr.environment != None:
+ args += [
+ "--environment",
+ json.encode(struct(arg = rctx.attr.environment)),
+ ]
+
+ return args
+
+def _create_repository_execution_environment(rctx):
+ """Create a environment dictionary for processes we spawn with rctx.execute.
+
+ Args:
+ rctx: The repository context.
+ Returns:
+ Dictionary of environment variable suitable to pass to rctx.execute.
+ """
+
+ # Gather any available CPPFLAGS values
+ cppflags = []
+ cppflags.extend(_get_xcode_location_cflags(rctx))
+ cppflags.extend(_get_toolchain_unix_cflags(rctx))
+
+ env = {
+ "PYTHONPATH": _construct_pypath(rctx),
+ CPPFLAGS: " ".join(cppflags),
+ }
+
+ return env
+
+_BUILD_FILE_CONTENTS = """\
+package(default_visibility = ["//visibility:public"])
+
+# Ensure the `requirements.bzl` source can be accessed by stardoc, since users load() from it
+exports_files(["requirements.bzl"])
+"""
+
+def locked_requirements_label(ctx, attr):
+ """Get the preferred label for a locked requirements file based on platform.
+
+ Args:
+ ctx: repository or module context
+ attr: attributes for the repo rule or tag extension
+
+ Returns:
+ Label
+ """
+ os = ctx.os.name.lower()
+ requirements_txt = attr.requirements_lock
+ if os.startswith("mac os") and attr.requirements_darwin != None:
+ requirements_txt = attr.requirements_darwin
+ elif os.startswith("linux") and attr.requirements_linux != None:
+ requirements_txt = attr.requirements_linux
+ elif "win" in os and attr.requirements_windows != None:
+ requirements_txt = attr.requirements_windows
+ if not requirements_txt:
+ fail("""\
+A requirements_lock attribute must be specified, or a platform-specific lockfile using one of the requirements_* attributes.
+""")
+ return requirements_txt
+
+def _pkg_aliases(rctx, repo_name, bzl_packages):
+ """Create alias declarations for each python dependency.
+
+ The aliases should be appended to the pip_repository BUILD.bazel file. These aliases
+ allow users to use requirement() without needed a corresponding `use_repo()` for each dep
+ when using bzlmod.
+
+ Args:
+ rctx: the repository context.
+ repo_name: the repository name of the parent that is visible to the users.
+ bzl_packages: the list of packages to setup.
+ """
+ for name in bzl_packages:
+ build_content = """package(default_visibility = ["//visibility:public"])
+
+alias(
+ name = "{name}",
+ actual = "@{repo_name}_{dep}//:pkg",
+)
+
+alias(
+ name = "pkg",
+ actual = "@{repo_name}_{dep}//:pkg",
+)
+
+alias(
+ name = "whl",
+ actual = "@{repo_name}_{dep}//:whl",
+)
+
+alias(
+ name = "data",
+ actual = "@{repo_name}_{dep}//:data",
+)
+
+alias(
+ name = "dist_info",
+ actual = "@{repo_name}_{dep}//:dist_info",
+)
+""".format(
+ name = name,
+ repo_name = repo_name,
+ dep = name,
+ )
+ rctx.file("{}/BUILD.bazel".format(name), build_content)
+
+def _create_pip_repository_bzlmod(rctx, bzl_packages, requirements):
+ repo_name = rctx.attr.repo_name
+ build_contents = _BUILD_FILE_CONTENTS
+ _pkg_aliases(rctx, repo_name, bzl_packages)
+
+ # NOTE: we are using the canonical name with the double '@' in order to
+ # always uniquely identify a repository, as the labels are being passed as
+ # a string and the resolution of the label happens at the call-site of the
+ # `requirement`, et al. macros.
+ macro_tmpl = "@@{name}//{{}}:{{}}".format(name = rctx.attr.name)
+
+ rctx.file("BUILD.bazel", build_contents)
+ rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
+ "%%ALL_DATA_REQUIREMENTS%%": _format_repr_list([
+ macro_tmpl.format(p, "data")
+ for p in bzl_packages
+ ]),
+ "%%ALL_REQUIREMENTS%%": _format_repr_list([
+ macro_tmpl.format(p, p)
+ for p in bzl_packages
+ ]),
+ "%%ALL_WHL_REQUIREMENTS%%": _format_repr_list([
+ macro_tmpl.format(p, "whl")
+ for p in bzl_packages
+ ]),
+ "%%MACRO_TMPL%%": macro_tmpl,
+ "%%NAME%%": rctx.attr.name,
+ "%%REQUIREMENTS_LOCK%%": requirements,
+ })
+
+def _pip_hub_repository_bzlmod_impl(rctx):
+ bzl_packages = rctx.attr.whl_library_alias_names
+ _create_pip_repository_bzlmod(rctx, bzl_packages, "")
+
+pip_hub_repository_bzlmod_attrs = {
+ "repo_name": attr.string(
+ mandatory = True,
+ doc = "The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name.",
+ ),
+ "whl_library_alias_names": attr.string_list(
+ mandatory = True,
+ doc = "The list of whl alias that we use to build aliases and the whl names",
+ ),
+ "_template": attr.label(
+ default = ":pip_hub_repository_requirements_bzlmod.bzl.tmpl",
+ ),
+}
+
+pip_hub_repository_bzlmod = repository_rule(
+ attrs = pip_hub_repository_bzlmod_attrs,
+ doc = """A rule for bzlmod mulitple pip repository creation. PRIVATE USE ONLY.""",
+ implementation = _pip_hub_repository_bzlmod_impl,
+)
+
+def _pip_repository_bzlmod_impl(rctx):
+ requirements_txt = locked_requirements_label(rctx, rctx.attr)
+ content = rctx.read(requirements_txt)
+ parsed_requirements_txt = parse_requirements(content)
+
+ packages = [(normalize_name(name), requirement) for name, requirement in parsed_requirements_txt.requirements]
+
+ bzl_packages = sorted([name for name, _ in packages])
+ _create_pip_repository_bzlmod(rctx, bzl_packages, str(requirements_txt))
+
+pip_repository_bzlmod_attrs = {
+ "repo_name": attr.string(
+ mandatory = True,
+ doc = "The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name",
+ ),
+ "requirements_darwin": attr.label(
+ allow_single_file = True,
+ doc = "Override the requirements_lock attribute when the host platform is Mac OS",
+ ),
+ "requirements_linux": attr.label(
+ allow_single_file = True,
+ doc = "Override the requirements_lock attribute when the host platform is Linux",
+ ),
+ "requirements_lock": attr.label(
+ allow_single_file = True,
+ doc = """
+A fully resolved 'requirements.txt' pip requirement file containing the transitive set of your dependencies. If this file is passed instead
+of 'requirements' no resolve will take place and pip_repository will create individual repositories for each of your dependencies so that
+wheels are fetched/built only for the targets specified by 'build/run/test'.
+""",
+ ),
+ "requirements_windows": attr.label(
+ allow_single_file = True,
+ doc = "Override the requirements_lock attribute when the host platform is Windows",
+ ),
+ "_template": attr.label(
+ default = ":pip_repository_requirements_bzlmod.bzl.tmpl",
+ ),
+}
+
+pip_repository_bzlmod = repository_rule(
+ attrs = pip_repository_bzlmod_attrs,
+ doc = """A rule for bzlmod pip_repository creation. Intended for private use only.""",
+ implementation = _pip_repository_bzlmod_impl,
+)
+
+def _pip_repository_impl(rctx):
+ requirements_txt = locked_requirements_label(rctx, rctx.attr)
+ content = rctx.read(requirements_txt)
+ parsed_requirements_txt = parse_requirements(content)
+
+ packages = [(normalize_name(name), requirement) for name, requirement in parsed_requirements_txt.requirements]
+
+ bzl_packages = sorted([name for name, _ in packages])
+
+ imports = [
+ 'load("@rules_python//python/pip_install:pip_repository.bzl", "whl_library")',
+ ]
+
+ annotations = {}
+ for pkg, annotation in rctx.attr.annotations.items():
+ filename = "{}.annotation.json".format(normalize_name(pkg))
+ rctx.file(filename, json.encode_indent(json.decode(annotation)))
+ annotations[pkg] = "@{name}//:{filename}".format(name = rctx.attr.name, filename = filename)
+
+ tokenized_options = []
+ for opt in parsed_requirements_txt.options:
+ for p in opt.split(" "):
+ tokenized_options.append(p)
+
+ options = tokenized_options + rctx.attr.extra_pip_args
+
+ config = {
+ "download_only": rctx.attr.download_only,
+ "enable_implicit_namespace_pkgs": rctx.attr.enable_implicit_namespace_pkgs,
+ "environment": rctx.attr.environment,
+ "extra_pip_args": options,
+ "isolated": use_isolated(rctx, rctx.attr),
+ "pip_data_exclude": rctx.attr.pip_data_exclude,
+ "python_interpreter": _get_python_interpreter_attr(rctx),
+ "quiet": rctx.attr.quiet,
+ "repo": rctx.attr.name,
+ "repo_prefix": "{}_".format(rctx.attr.name),
+ "timeout": rctx.attr.timeout,
+ }
+
+ if rctx.attr.python_interpreter_target:
+ config["python_interpreter_target"] = str(rctx.attr.python_interpreter_target)
+
+ if rctx.attr.incompatible_generate_aliases:
+ _pkg_aliases(rctx, rctx.attr.name, bzl_packages)
+
+ rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
+ rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
+ "%%ALL_DATA_REQUIREMENTS%%": _format_repr_list([
+ "@{}//{}:data".format(rctx.attr.name, p) if rctx.attr.incompatible_generate_aliases else "@{}_{}//:data".format(rctx.attr.name, p)
+ for p in bzl_packages
+ ]),
+ "%%ALL_REQUIREMENTS%%": _format_repr_list([
+ "@{}//{}".format(rctx.attr.name, p) if rctx.attr.incompatible_generate_aliases else "@{}_{}//:pkg".format(rctx.attr.name, p)
+ for p in bzl_packages
+ ]),
+ "%%ALL_WHL_REQUIREMENTS%%": _format_repr_list([
+ "@{}//{}:whl".format(rctx.attr.name, p) if rctx.attr.incompatible_generate_aliases else "@{}_{}//:whl".format(rctx.attr.name, p)
+ for p in bzl_packages
+ ]),
+ "%%ANNOTATIONS%%": _format_dict(_repr_dict(annotations)),
+ "%%CONFIG%%": _format_dict(_repr_dict(config)),
+ "%%EXTRA_PIP_ARGS%%": json.encode(options),
+ "%%IMPORTS%%": "\n".join(sorted(imports)),
+ "%%NAME%%": rctx.attr.name,
+ "%%PACKAGES%%": _format_repr_list(
+ [
+ ("{}_{}".format(rctx.attr.name, p), r)
+ for p, r in packages
+ ],
+ ),
+ "%%REQUIREMENTS_LOCK%%": str(requirements_txt),
+ })
+
+ return
+
+common_env = [
+ "RULES_PYTHON_PIP_ISOLATED",
+]
+
+common_attrs = {
+ "download_only": attr.bool(
+ doc = """
+Whether to use "pip download" instead of "pip wheel". Disables building wheels from source, but allows use of
+--platform, --python-version, --implementation, and --abi in --extra_pip_args to download wheels for a different
+platform from the host platform.
+ """,
+ ),
+ "enable_implicit_namespace_pkgs": attr.bool(
+ default = False,
+ doc = """
+If true, disables conversion of native namespace packages into pkg-util style namespace packages. When set all py_binary
+and py_test targets must specify either `legacy_create_init=False` or the global Bazel option
+`--incompatible_default_to_explicit_init_py` to prevent `__init__.py` being automatically generated in every directory.
+
+This option is required to support some packages which cannot handle the conversion to pkg-util style.
+ """,
+ ),
+ "environment": attr.string_dict(
+ doc = """
+Environment variables to set in the pip subprocess.
+Can be used to set common variables such as `http_proxy`, `https_proxy` and `no_proxy`
+Note that pip is run with "--isolated" on the CLI so `PIP_<VAR>_<NAME>`
+style env vars are ignored, but env vars that control requests and urllib3
+can be passed.
+ """,
+ default = {},
+ ),
+ "extra_pip_args": attr.string_list(
+ doc = "Extra arguments to pass on to pip. Must not contain spaces.",
+ ),
+ "isolated": attr.bool(
+ doc = """\
+Whether or not to pass the [--isolated](https://pip.pypa.io/en/stable/cli/pip/#cmdoption-isolated) flag to
+the underlying pip command. Alternatively, the `RULES_PYTHON_PIP_ISOLATED` environment variable can be used
+to control this flag.
+""",
+ default = True,
+ ),
+ "pip_data_exclude": attr.string_list(
+ doc = "Additional data exclusion parameters to add to the pip packages BUILD file.",
+ ),
+ "python_interpreter": attr.string(
+ doc = """\
+The python interpreter to use. This can either be an absolute path or the name
+of a binary found on the host's `PATH` environment variable. If no value is set
+`python3` is defaulted for Unix systems and `python.exe` for Windows.
+""",
+ # NOTE: This attribute should not have a default. See `_get_python_interpreter_attr`
+ # default = "python3"
+ ),
+ "python_interpreter_target": attr.label(
+ allow_single_file = True,
+ doc = """
+If you are using a custom python interpreter built by another repository rule,
+use this attribute to specify its BUILD target. This allows pip_repository to invoke
+pip using the same interpreter as your toolchain. If set, takes precedence over
+python_interpreter. An example value: "@python3_x86_64-unknown-linux-gnu//:python".
+""",
+ ),
+ "quiet": attr.bool(
+ default = True,
+ doc = "If True, suppress printing stdout and stderr output to the terminal.",
+ ),
+ "repo_prefix": attr.string(
+ doc = """
+Prefix for the generated packages will be of the form `@<prefix><sanitized-package-name>//...`
+""",
+ ),
+ # 600 is documented as default here: https://docs.bazel.build/versions/master/skylark/lib/repository_ctx.html#execute
+ "timeout": attr.int(
+ default = 600,
+ doc = "Timeout (in seconds) on the rule's execution duration.",
+ ),
+ "_py_srcs": attr.label_list(
+ doc = "Python sources used in the repository rule",
+ allow_files = True,
+ default = PIP_INSTALL_PY_SRCS,
+ ),
+}
+
+pip_repository_attrs = {
+ "annotations": attr.string_dict(
+ doc = "Optional annotations to apply to packages",
+ ),
+ "incompatible_generate_aliases": attr.bool(
+ default = False,
+ doc = "Allow generating aliases '@pip//<pkg>' -> '@pip_<pkg>//:pkg'.",
+ ),
+ "requirements_darwin": attr.label(
+ allow_single_file = True,
+ doc = "Override the requirements_lock attribute when the host platform is Mac OS",
+ ),
+ "requirements_linux": attr.label(
+ allow_single_file = True,
+ doc = "Override the requirements_lock attribute when the host platform is Linux",
+ ),
+ "requirements_lock": attr.label(
+ allow_single_file = True,
+ doc = """
+A fully resolved 'requirements.txt' pip requirement file containing the transitive set of your dependencies. If this file is passed instead
+of 'requirements' no resolve will take place and pip_repository will create individual repositories for each of your dependencies so that
+wheels are fetched/built only for the targets specified by 'build/run/test'.
+""",
+ ),
+ "requirements_windows": attr.label(
+ allow_single_file = True,
+ doc = "Override the requirements_lock attribute when the host platform is Windows",
+ ),
+ "_template": attr.label(
+ default = ":pip_repository_requirements.bzl.tmpl",
+ ),
+}
+
+pip_repository_attrs.update(**common_attrs)
+
+pip_repository = repository_rule(
+ attrs = pip_repository_attrs,
+ doc = """A rule for importing `requirements.txt` dependencies into Bazel.
+
+This rule imports a `requirements.txt` file and generates a new
+`requirements.bzl` file. This is used via the `WORKSPACE` pattern:
+
+```python
+pip_repository(
+ name = "foo",
+ requirements = ":requirements.txt",
+)
+```
+
+You can then reference imported dependencies from your `BUILD` file with:
+
+```python
+load("@foo//:requirements.bzl", "requirement")
+py_library(
+ name = "bar",
+ ...
+ deps = [
+ "//my/other:dep",
+ requirement("requests"),
+ requirement("numpy"),
+ ],
+)
+```
+
+Or alternatively:
+```python
+load("@foo//:requirements.bzl", "all_requirements")
+py_binary(
+ name = "baz",
+ ...
+ deps = [
+ ":foo",
+ ] + all_requirements,
+)
+```
+""",
+ implementation = _pip_repository_impl,
+ environ = common_env,
+)
+
+def _whl_library_impl(rctx):
+ python_interpreter = _resolve_python_interpreter(rctx)
+ args = [
+ python_interpreter,
+ "-m",
+ "python.pip_install.tools.wheel_installer.wheel_installer",
+ "--requirement",
+ rctx.attr.requirement,
+ "--repo",
+ rctx.attr.repo,
+ "--repo-prefix",
+ rctx.attr.repo_prefix,
+ ]
+ if rctx.attr.annotation:
+ args.extend([
+ "--annotation",
+ rctx.path(rctx.attr.annotation),
+ ])
+
+ args = _parse_optional_attrs(rctx, args)
+
+ result = rctx.execute(
+ args,
+ # Manually construct the PYTHONPATH since we cannot use the toolchain here
+ environment = _create_repository_execution_environment(rctx),
+ quiet = rctx.attr.quiet,
+ timeout = rctx.attr.timeout,
+ )
+
+ if result.return_code:
+ fail("whl_library %s failed: %s (%s) error code: '%s'" % (rctx.attr.name, result.stdout, result.stderr, result.return_code))
+
+ return
+
+whl_library_attrs = {
+ "annotation": attr.label(
+ doc = (
+ "Optional json encoded file containing annotation to apply to the extracted wheel. " +
+ "See `package_annotation`"
+ ),
+ allow_files = True,
+ ),
+ "repo": attr.string(
+ mandatory = True,
+ doc = "Pointer to parent repo name. Used to make these rules rerun if the parent repo changes.",
+ ),
+ "requirement": attr.string(
+ mandatory = True,
+ doc = "Python requirement string describing the package to make available",
+ ),
+}
+
+whl_library_attrs.update(**common_attrs)
+
+whl_library = repository_rule(
+ attrs = whl_library_attrs,
+ doc = """
+Download and extracts a single wheel based into a bazel repo based on the requirement string passed in.
+Instantiated from pip_repository and inherits config options from there.""",
+ implementation = _whl_library_impl,
+ environ = common_env,
+)
+
+def package_annotation(
+ additive_build_content = None,
+ copy_files = {},
+ copy_executables = {},
+ data = [],
+ data_exclude_glob = [],
+ srcs_exclude_glob = []):
+ """Annotations to apply to the BUILD file content from package generated from a `pip_repository` rule.
+
+ [cf]: https://github.com/bazelbuild/bazel-skylib/blob/main/docs/copy_file_doc.md
+
+ Args:
+ additive_build_content (str, optional): Raw text to add to the generated `BUILD` file of a package.
+ copy_files (dict, optional): A mapping of `src` and `out` files for [@bazel_skylib//rules:copy_file.bzl][cf]
+ copy_executables (dict, optional): A mapping of `src` and `out` files for
+ [@bazel_skylib//rules:copy_file.bzl][cf]. Targets generated here will also be flagged as
+ executable.
+ data (list, optional): A list of labels to add as `data` dependencies to the generated `py_library` target.
+ data_exclude_glob (list, optional): A list of exclude glob patterns to add as `data` to the generated
+ `py_library` target.
+ srcs_exclude_glob (list, optional): A list of labels to add as `srcs` to the generated `py_library` target.
+
+ Returns:
+ str: A json encoded string of the provided content.
+ """
+ return json.encode(struct(
+ additive_build_content = additive_build_content,
+ copy_files = copy_files,
+ copy_executables = copy_executables,
+ data = data,
+ data_exclude_glob = data_exclude_glob,
+ srcs_exclude_glob = srcs_exclude_glob,
+ ))
+
+# pip_repository implementation
+
+def _format_list(items):
+ return "[{}]".format(", ".join(items))
+
+def _format_repr_list(strings):
+ return _format_list(
+ [repr(s) for s in strings],
+ )
+
+def _repr_dict(items):
+ return {k: repr(v) for k, v in items.items()}
+
+def _format_dict(items):
+ return "{{{}}}".format(", ".join(sorted(['"{}": {}'.format(k, v) for k, v in items.items()])))
diff --git a/python/pip_install/pip_repository_requirements.bzl.tmpl b/python/pip_install/pip_repository_requirements.bzl.tmpl
new file mode 100644
index 0000000..411f334
--- /dev/null
+++ b/python/pip_install/pip_repository_requirements.bzl.tmpl
@@ -0,0 +1,54 @@
+"""Starlark representation of locked requirements.
+
+@generated by rules_python pip_parse repository rule
+from %%REQUIREMENTS_LOCK%%
+"""
+
+%%IMPORTS%%
+
+all_requirements = %%ALL_REQUIREMENTS%%
+
+all_whl_requirements = %%ALL_WHL_REQUIREMENTS%%
+
+all_data_requirements = %%ALL_DATA_REQUIREMENTS%%
+
+_packages = %%PACKAGES%%
+_config = %%CONFIG%%
+_annotations = %%ANNOTATIONS%%
+
+def _clean_name(name):
+ return name.replace("-", "_").replace(".", "_").lower()
+
+def requirement(name):
+ return "@%%NAME%%_" + _clean_name(name) + "//:pkg"
+
+def whl_requirement(name):
+ return "@%%NAME%%_" + _clean_name(name) + "//:whl"
+
+def data_requirement(name):
+ return "@%%NAME%%_" + _clean_name(name) + "//:data"
+
+def dist_info_requirement(name):
+ return "@%%NAME%%_" + _clean_name(name) + "//:dist_info"
+
+def entry_point(pkg, script = None):
+ if not script:
+ script = pkg
+ return "@%%NAME%%_" + _clean_name(pkg) + "//:rules_python_wheel_entry_point_" + script
+
+def _get_annotation(requirement):
+ # This expects to parse `setuptools==58.2.0 --hash=sha256:2551203ae6955b9876741a26ab3e767bb3242dafe86a32a749ea0d78b6792f11`
+ # down to `setuptools`.
+ name = requirement.split(" ")[0].split("=")[0].split("[")[0]
+ return _annotations.get(name)
+
+def install_deps(**whl_library_kwargs):
+ whl_config = dict(_config)
+ whl_config.update(whl_library_kwargs)
+ for name, requirement in _packages:
+ whl_library(
+ name = name,
+ requirement = requirement,
+ annotation = _get_annotation(requirement),
+ **whl_config
+ )
diff --git a/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl b/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
new file mode 100644
index 0000000..2df60b0
--- /dev/null
+++ b/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
@@ -0,0 +1,33 @@
+"""Starlark representation of locked requirements.
+
+@generated by rules_python pip_parse repository rule
+from %%REQUIREMENTS_LOCK%%.
+"""
+
+all_requirements = %%ALL_REQUIREMENTS%%
+
+all_whl_requirements = %%ALL_WHL_REQUIREMENTS%%
+
+all_data_requirements = %%ALL_DATA_REQUIREMENTS%%
+
+def _clean_name(name):
+ return name.replace("-", "_").replace(".", "_").lower()
+
+def requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "pkg")
+
+def whl_requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "whl")
+
+def data_requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "data")
+
+def dist_info_requirement(name):
+ return "%%MACRO_TMPL%%".format(_clean_name(name), "dist_info")
+
+def entry_point(pkg, script = None):
+ """entry_point returns the target of the canonical label of the package entrypoints.
+ """
+ if not script:
+ script = pkg
+ return "@@%%NAME%%_{}//:rules_python_wheel_entry_point_{}".format(_clean_name(pkg), script)
diff --git a/python/pip_install/private/BUILD.bazel b/python/pip_install/private/BUILD.bazel
new file mode 100644
index 0000000..86b4b3d
--- /dev/null
+++ b/python/pip_install/private/BUILD.bazel
@@ -0,0 +1,24 @@
+load(":pip_install_utils.bzl", "srcs_module")
+
+package(default_visibility = ["//:__subpackages__"])
+
+exports_files([
+ "srcs.bzl",
+])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["*"]),
+ visibility = ["//python/pip_install:__subpackages__"],
+)
+
+filegroup(
+ name = "bzl_srcs",
+ srcs = glob(["*.bzl"]),
+)
+
+srcs_module(
+ name = "srcs_module",
+ srcs = "//python/pip_install:py_srcs",
+ dest = ":srcs.bzl",
+)
diff --git a/python/pip_install/private/pip_install_utils.bzl b/python/pip_install/private/pip_install_utils.bzl
new file mode 100644
index 0000000..488583d
--- /dev/null
+++ b/python/pip_install/private/pip_install_utils.bzl
@@ -0,0 +1,132 @@
+# 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.
+
+"""Utilities for `rules_python` pip rules"""
+
+_SRCS_TEMPLATE = """\
+\"\"\"A generated file containing all source files used for `@rules_python//python/pip_install:pip_repository.bzl` rules
+
+This file is auto-generated from the `@rules_python//python/pip_install/private:srcs_module.update` target. Please
+`bazel run` this target to apply any updates. Note that doing so will discard any local modifications.
+"\"\"
+
+# Each source file is tracked as a target so `pip_repository` rules will know to automatically rebuild if any of the
+# sources changed.
+PIP_INSTALL_PY_SRCS = [
+ {srcs}
+]
+"""
+
+def _src_label(file):
+ dir_path, file_name = file.short_path.rsplit("/", 1)
+
+ return "@rules_python//{}:{}".format(
+ dir_path,
+ file_name,
+ )
+
+def _srcs_module_impl(ctx):
+ srcs = [_src_label(src) for src in ctx.files.srcs]
+ if not srcs:
+ fail("`srcs` cannot be empty")
+ output = ctx.actions.declare_file(ctx.label.name)
+
+ ctx.actions.write(
+ output = output,
+ content = _SRCS_TEMPLATE.format(
+ srcs = "\n ".join(["\"{}\",".format(src) for src in srcs]),
+ ),
+ )
+
+ return DefaultInfo(
+ files = depset([output]),
+ )
+
+_srcs_module = rule(
+ doc = "A rule for writing a list of sources to a templated file",
+ implementation = _srcs_module_impl,
+ attrs = {
+ "srcs": attr.label(
+ doc = "A filegroup of source files",
+ allow_files = True,
+ ),
+ },
+)
+
+_INSTALLER_TEMPLATE = """\
+#!/bin/bash
+set -euo pipefail
+cp -f "{path}" "${{BUILD_WORKSPACE_DIRECTORY}}/{dest}"
+"""
+
+def _srcs_updater_impl(ctx):
+ output = ctx.actions.declare_file(ctx.label.name + ".sh")
+ target_file = ctx.file.input
+ dest = ctx.file.dest.short_path
+
+ ctx.actions.write(
+ output = output,
+ content = _INSTALLER_TEMPLATE.format(
+ path = target_file.short_path,
+ dest = dest,
+ ),
+ is_executable = True,
+ )
+
+ return DefaultInfo(
+ files = depset([output]),
+ runfiles = ctx.runfiles(files = [target_file]),
+ executable = output,
+ )
+
+_srcs_updater = rule(
+ doc = "A rule for writing a `srcs.bzl` file back to the repository",
+ implementation = _srcs_updater_impl,
+ attrs = {
+ "dest": attr.label(
+ doc = "The target file to write the new `input` to.",
+ allow_single_file = ["srcs.bzl"],
+ mandatory = True,
+ ),
+ "input": attr.label(
+ doc = "The file to write back to the repository",
+ allow_single_file = True,
+ mandatory = True,
+ ),
+ },
+ executable = True,
+)
+
+def srcs_module(name, dest, **kwargs):
+ """A helper rule to ensure `pip_repository` rules are always up to date
+
+ Args:
+ name (str): The name of the sources module
+ dest (str): The filename the module should be written as in the current package.
+ **kwargs (dict): Additional keyword arguments
+ """
+ tags = kwargs.pop("tags", [])
+
+ _srcs_module(
+ name = name,
+ tags = tags,
+ **kwargs
+ )
+
+ _srcs_updater(
+ name = name + ".update",
+ input = name,
+ dest = dest,
+ tags = tags,
+ )
diff --git a/python/pip_install/private/srcs.bzl b/python/pip_install/private/srcs.bzl
new file mode 100644
index 0000000..f3064a3
--- /dev/null
+++ b/python/pip_install/private/srcs.bzl
@@ -0,0 +1,19 @@
+"""A generated file containing all source files used for `@rules_python//python/pip_install:pip_repository.bzl` rules
+
+This file is auto-generated from the `@rules_python//python/pip_install/private:srcs_module.update` target. Please
+`bazel run` this target to apply any updates. Note that doing so will discard any local modifications.
+"""
+
+# Each source file is tracked as a target so `pip_repository` rules will know to automatically rebuild if any of the
+# sources changed.
+PIP_INSTALL_PY_SRCS = [
+ "@rules_python//python/pip_install/tools/dependency_resolver:__init__.py",
+ "@rules_python//python/pip_install/tools/dependency_resolver:dependency_resolver.py",
+ "@rules_python//python/pip_install/tools/lib:__init__.py",
+ "@rules_python//python/pip_install/tools/lib:annotation.py",
+ "@rules_python//python/pip_install/tools/lib:arguments.py",
+ "@rules_python//python/pip_install/tools/lib:bazel.py",
+ "@rules_python//python/pip_install/tools/wheel_installer:namespace_pkgs.py",
+ "@rules_python//python/pip_install/tools/wheel_installer:wheel.py",
+ "@rules_python//python/pip_install/tools/wheel_installer:wheel_installer.py",
+]
diff --git a/python/pip_install/private/test/BUILD.bazel b/python/pip_install/private/test/BUILD.bazel
new file mode 100644
index 0000000..d4978f3
--- /dev/null
+++ b/python/pip_install/private/test/BUILD.bazel
@@ -0,0 +1,20 @@
+load("@bazel_skylib//rules:diff_test.bzl", "diff_test")
+load(":requirements_parser_tests.bzl", parse_requirements_tests = "parse_tests")
+
+diff_test(
+ name = "srcs_diff_test",
+ failure_message = (
+ "Please run 'bazel run //python/pip_install/private:srcs_module.update' " +
+ "to update the 'srcs.bzl' module found in the same package."
+ ),
+ file1 = "//python/pip_install/private:srcs_module",
+ file2 = "//python/pip_install/private:srcs.bzl",
+ # TODO: The diff_test here fails on Windows. As does the
+ # install script. This should be fixed.
+ target_compatible_with = select({
+ "@platforms//os:windows": ["@platforms//:incompatible"],
+ "//conditions:default": [],
+ }),
+)
+
+parse_requirements_tests(name = "test_parse_requirements")
diff --git a/python/pip_install/private/test/requirements_parser_tests.bzl b/python/pip_install/private/test/requirements_parser_tests.bzl
new file mode 100644
index 0000000..5ea742e
--- /dev/null
+++ b/python/pip_install/private/test/requirements_parser_tests.bzl
@@ -0,0 +1,224 @@
+# 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.
+
+"Unit tests for yaml.bzl"
+
+load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
+load("//python/pip_install:requirements_parser.bzl", "parse")
+
+def _parse_basic_test_impl(ctx):
+ env = unittest.begin(ctx)
+
+ # Base cases
+ asserts.equals(env, [], parse("").requirements)
+ asserts.equals(env, [], parse("\n").requirements)
+
+ # Various requirement specifiers (https://pip.pypa.io/en/stable/reference/requirement-specifiers/#requirement-specifiers)
+ asserts.equals(env, [("SomeProject", "SomeProject")], parse("SomeProject\n").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject == 1.3")], parse("SomeProject == 1.3\n").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject >= 1.2, < 2.0")], parse("SomeProject >= 1.2, < 2.0\n").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject[foo, bar]")], parse("SomeProject[foo, bar]\n").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject ~= 1.4.2")], parse("SomeProject ~= 1.4.2\n").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject == 5.4 ; python_version < '3.8'")], parse("SomeProject == 5.4 ; python_version < '3.8'\n").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject ; sys_platform == 'win32'")], parse("SomeProject ; sys_platform == 'win32'\n").requirements)
+ asserts.equals(env, [("requests", "requests [security] >= 2.8.1, == 2.8.* ; python_version < 2.7")], parse("requests [security] >= 2.8.1, == 2.8.* ; python_version < 2.7\n").requirements)
+
+ # Multiple requirements
+ asserts.equals(env, [("FooProject", "FooProject==1.0.0"), ("BarProject", "BarProject==2.0.0")], parse("""\
+FooProject==1.0.0
+BarProject==2.0.0
+""").requirements)
+
+ asserts.equals(env, [("FooProject", "FooProject==1.0.0"), ("BarProject", "BarProject==2.0.0")], parse("""\
+FooProject==1.0.0
+
+BarProject==2.0.0
+""").requirements)
+
+ # Comments
+ asserts.equals(env, [("SomeProject", "SomeProject")], parse("""\
+# This is a comment
+SomeProject
+""").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject")], parse("""\
+SomeProject
+# This is a comment
+""").requirements)
+ asserts.equals(env, [("SomeProject", "SomeProject == 1.3")], parse("""\
+SomeProject == 1.3 # This is a comment
+""").requirements)
+ asserts.equals(env, [("FooProject", "FooProject==1.0.0"), ("BarProject", "BarProject==2.0.0")], parse("""\
+FooProject==1.0.0
+# Comment
+BarProject==2.0.0 #Comment
+""").requirements)
+ asserts.equals(env, [("requests", "requests @ https://github.com/psf/requests/releases/download/v2.29.0/requests-2.29.0.tar.gz#sha1=3897c249b51a1a405d615a8c9cb92e5fdbf0dd49")], parse("""\
+requests @ https://github.com/psf/requests/releases/download/v2.29.0/requests-2.29.0.tar.gz#sha1=3897c249b51a1a405d615a8c9cb92e5fdbf0dd49
+""").requirements)
+
+ # Multiline
+ asserts.equals(env, [("certifi", "certifi==2021.10.8 --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569")], parse("""\
+certifi==2021.10.8 \
+ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
+ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
+ # via requests
+""").requirements)
+ asserts.equals(env, [("requests", "requests @ https://github.com/psf/requests/releases/download/v2.29.0/requests-2.29.0.tar.gz#sha1=3897c249b51a1a405d615a8c9cb92e5fdbf0dd49 --hash=sha256:eca58eb564b134e4ff521a02aa6f566c653835753e1fc8a50a20cb6bee4673cd")], parse("""\
+requests @ https://github.com/psf/requests/releases/download/v2.29.0/requests-2.29.0.tar.gz#sha1=3897c249b51a1a405d615a8c9cb92e5fdbf0dd49 \
+ --hash=sha256:eca58eb564b134e4ff521a02aa6f566c653835753e1fc8a50a20cb6bee4673cd
+ # via requirements.txt
+""").requirements)
+
+ # Options
+ asserts.equals(env, ["--pre"], parse("--pre\n").options)
+ asserts.equals(env, ["--find-links", "/my/local/archives"], parse("--find-links /my/local/archives\n").options)
+ asserts.equals(env, ["--pre", "--find-links", "/my/local/archives"], parse("""\
+--pre
+--find-links /my/local/archives
+""").options)
+ asserts.equals(env, ["--pre", "--find-links", "/my/local/archives"], parse("""\
+--pre # Comment
+--find-links /my/local/archives
+""").options)
+ asserts.equals(env, struct(requirements = [("FooProject", "FooProject==1.0.0")], options = ["--pre", "--find-links", "/my/local/archives"]), parse("""\
+--pre # Comment
+FooProject==1.0.0
+--find-links /my/local/archives
+"""))
+
+ return unittest.end(env)
+
+def _parse_requirements_lockfile_test_impl(ctx):
+ env = unittest.begin(ctx)
+
+ asserts.equals(env, [
+ ("certifi", "certifi==2021.10.8 --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"),
+ ("chardet", "chardet==4.0.0 --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"),
+ ("idna", "idna==2.10 --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0"),
+ ("pathspec", "pathspec==0.9.0 --hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a --hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"),
+ ("python-dateutil", "python-dateutil==2.8.2 --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"),
+ ("python-magic", "python-magic==0.4.24 --hash=sha256:4fec8ee805fea30c07afccd1592c0f17977089895bdfaae5fec870a84e997626 --hash=sha256:de800df9fb50f8ec5974761054a708af6e4246b03b4bdaee993f948947b0ebcf"),
+ ("pyyaml", "pyyaml==6.0 --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5"),
+ ("requests", "requests==2.25.1 --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"),
+ ("s3cmd", "s3cmd==2.1.0 --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03"),
+ ("six", "six==1.16.0 --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"),
+ ("urllib3", "urllib3==1.26.7 --hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece --hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"),
+ ("yamllint", "yamllint==1.26.3 --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e"),
+ ("setuptools", "setuptools==59.6.0 --hash=sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373 --hash=sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e"),
+ ], parse("""\
+#
+# This file is autogenerated by pip-compile with python 3.9
+# To update, run:
+#
+# bazel run //:requirements.update
+#
+certifi==2021.10.8 \
+ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
+ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
+ # via requests
+chardet==4.0.0 \
+ --hash=sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa \
+ --hash=sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5
+ # via requests
+idna==2.10 \
+ --hash=sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6 \
+ --hash=sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0
+ # via requests
+pathspec==0.9.0 \
+ --hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
+ --hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
+ # via yamllint
+python-dateutil==2.8.2 \
+ --hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
+ --hash=sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9
+ # via s3cmd
+python-magic==0.4.24 \
+ --hash=sha256:4fec8ee805fea30c07afccd1592c0f17977089895bdfaae5fec870a84e997626 \
+ --hash=sha256:de800df9fb50f8ec5974761054a708af6e4246b03b4bdaee993f948947b0ebcf
+ # via s3cmd
+pyyaml==6.0 \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.25.1 \
+ --hash=sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804 \
+ --hash=sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e
+ # via -r requirements.in
+s3cmd==2.1.0 \
+ --hash=sha256:49cd23d516b17974b22b611a95ce4d93fe326feaa07320bd1d234fed68cbccfa \
+ --hash=sha256:966b0a494a916fc3b4324de38f089c86c70ee90e8e1cae6d59102103a4c0cc03
+ # via -r requirements.in
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via python-dateutil
+urllib3==1.26.7 \
+ --hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece \
+ --hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844
+ # via requests
+yamllint==1.26.3 \
+ --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e
+ # via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==59.6.0 \
+ --hash=sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373 \
+ --hash=sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e
+ # via yamllint
+""").requirements)
+
+ return unittest.end(env)
+
+parse_basic_test = unittest.make(
+ _parse_basic_test_impl,
+ attrs = {},
+)
+parse_requirements_lockfile_test = unittest.make(
+ _parse_requirements_lockfile_test_impl,
+ attrs = {},
+)
+
+def parse_tests(name):
+ unittest.suite(name, parse_basic_test, parse_requirements_lockfile_test)
diff --git a/python/pip_install/repositories.bzl b/python/pip_install/repositories.bzl
new file mode 100644
index 0000000..efe3bc7
--- /dev/null
+++ b/python/pip_install/repositories.bzl
@@ -0,0 +1,148 @@
+# 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.
+
+""
+
+load("@bazel_skylib//lib:versions.bzl", "versions")
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("//:version.bzl", "MINIMUM_BAZEL_VERSION")
+
+_RULE_DEPS = [
+ (
+ "pypi__build",
+ "https://files.pythonhosted.org/packages/03/97/f58c723ff036a8d8b4d3115377c0a37ed05c1f68dd9a0d66dab5e82c5c1c/build-0.9.0-py3-none-any.whl",
+ "38a7a2b7a0bdc61a42a0a67509d88c71ecfc37b393baba770fae34e20929ff69",
+ ),
+ (
+ "pypi__click",
+ "https://files.pythonhosted.org/packages/76/0a/b6c5f311e32aeb3b406e03c079ade51e905ea630fc19d1262a46249c1c86/click-8.0.1-py3-none-any.whl",
+ "fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6",
+ ),
+ (
+ "pypi__colorama",
+ "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl",
+ "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6",
+ ),
+ (
+ "pypi__installer",
+ "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl",
+ "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53",
+ ),
+ (
+ "pypi__packaging",
+ "https://files.pythonhosted.org/packages/8f/7b/42582927d281d7cb035609cd3a543ffac89b74f3f4ee8e1c50914bcb57eb/packaging-22.0-py3-none-any.whl",
+ "957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3",
+ ),
+ (
+ "pypi__pep517",
+ "https://files.pythonhosted.org/packages/ee/2f/ef63e64e9429111e73d3d6cbee80591672d16f2725e648ebc52096f3d323/pep517-0.13.0-py3-none-any.whl",
+ "4ba4446d80aed5b5eac6509ade100bff3e7943a8489de249654a5ae9b33ee35b",
+ ),
+ (
+ "pypi__pip",
+ "https://files.pythonhosted.org/packages/09/bd/2410905c76ee14c62baf69e3f4aa780226c1bbfc9485731ad018e35b0cb5/pip-22.3.1-py3-none-any.whl",
+ "908c78e6bc29b676ede1c4d57981d490cb892eb45cd8c214ab6298125119e077",
+ ),
+ (
+ "pypi__pip_tools",
+ "https://files.pythonhosted.org/packages/5e/e8/f6d7d1847c7351048da870417724ace5c4506e816b38db02f4d7c675c189/pip_tools-6.12.1-py3-none-any.whl",
+ "f0c0c0ec57b58250afce458e2e6058b1f30a4263db895b7d72fd6311bf1dc6f7",
+ ),
+ (
+ "pypi__setuptools",
+ "https://files.pythonhosted.org/packages/7c/5b/3d92b9f0f7ca1645cba48c080b54fe7d8b1033a4e5720091d1631c4266db/setuptools-60.10.0-py3-none-any.whl",
+ "782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96",
+ ),
+ (
+ "pypi__tomli",
+ "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl",
+ "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc",
+ ),
+ (
+ "pypi__wheel",
+ "https://files.pythonhosted.org/packages/bd/7c/d38a0b30ce22fc26ed7dbc087c6d00851fb3395e9d0dac40bec1f905030c/wheel-0.38.4-py3-none-any.whl",
+ "b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8",
+ ),
+ (
+ "pypi__importlib_metadata",
+ "https://files.pythonhosted.org/packages/d7/31/74dcb59a601b95fce3b0334e8fc9db758f78e43075f22aeb3677dfb19f4c/importlib_metadata-1.4.0-py2.py3-none-any.whl",
+ "bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359",
+ ),
+ (
+ "pypi__zipp",
+ "https://files.pythonhosted.org/packages/f4/50/cc72c5bcd48f6e98219fc4a88a5227e9e28b81637a99c49feba1d51f4d50/zipp-1.0.0-py2.py3-none-any.whl",
+ "8dda78f06bd1674bd8720df8a50bb47b6e1233c503a4eed8e7810686bde37656",
+ ),
+ (
+ "pypi__more_itertools",
+ "https://files.pythonhosted.org/packages/bd/3f/c4b3dbd315e248f84c388bd4a72b131a29f123ecacc37ffb2b3834546e42/more_itertools-8.13.0-py3-none-any.whl",
+ "c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb",
+ ),
+]
+
+_GENERIC_WHEEL = """\
+package(default_visibility = ["//visibility:public"])
+
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "lib",
+ srcs = glob(["**/*.py"]),
+ data = glob(["**/*"], exclude=[
+ # These entries include those put into user-installed dependencies by
+ # data_exclude in /python/pip_install/tools/bazel.py
+ # to avoid non-determinism following pip install's behavior.
+ "**/*.py",
+ "**/*.pyc",
+ "**/*.pyc.*", # During pyc creation, temp files named *.pyc.NNN are created
+ "**/* *",
+ "**/*.dist-info/RECORD",
+ "BUILD",
+ "WORKSPACE",
+ ]),
+ # This makes this directory a top-level in the python import
+ # search path for anything that depends on this.
+ imports = ["."],
+)
+"""
+
+# Collate all the repository names so they can be easily consumed
+all_requirements = [name for (name, _, _) in _RULE_DEPS]
+
+def requirement(pkg):
+ return Label("@pypi__" + pkg + "//:lib")
+
+def pip_install_dependencies():
+ """
+ Fetch dependencies these rules depend on. Workspaces that use the pip_install rule can call this.
+
+ (However we call it from pip_install, making it optional for users to do so.)
+ """
+
+ # We only support Bazel LTS and rolling releases.
+ # Give the user an obvious error to upgrade rather than some obscure missing symbol later.
+ # It's not guaranteed that users call this function, but it's used by all the pip fetch
+ # repository rules so it's likely that most users get the right error.
+ versions.check(MINIMUM_BAZEL_VERSION)
+
+ for (name, url, sha256) in _RULE_DEPS:
+ maybe(
+ http_archive,
+ name,
+ url = url,
+ sha256 = sha256,
+ type = "zip",
+ build_file_content = _GENERIC_WHEEL,
+ )
diff --git a/python/pip_install/requirements.bzl b/python/pip_install/requirements.bzl
new file mode 100644
index 0000000..84ee203
--- /dev/null
+++ b/python/pip_install/requirements.bzl
@@ -0,0 +1,143 @@
+# 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.
+
+"""Rules to verify and update pip-compile locked requirements.txt"""
+
+load("//python:defs.bzl", _py_binary = "py_binary", _py_test = "py_test")
+load("//python/pip_install:repositories.bzl", "requirement")
+
+def compile_pip_requirements(
+ name,
+ extra_args = [],
+ extra_deps = [],
+ generate_hashes = True,
+ py_binary = _py_binary,
+ py_test = _py_test,
+ requirements_in = None,
+ requirements_txt = None,
+ requirements_darwin = None,
+ requirements_linux = None,
+ requirements_windows = None,
+ visibility = ["//visibility:private"],
+ tags = None,
+ **kwargs):
+ """Generates targets for managing pip dependencies with pip-compile.
+
+ By default this rules generates a filegroup named "[name]" which can be included in the data
+ of some other compile_pip_requirements rule that references these requirements
+ (e.g. with `-r ../other/requirements.txt`).
+
+ It also generates two targets for running pip-compile:
+
+ - validate with `bazel test [name]_test`
+ - update with `bazel run [name].update`
+
+ If you are using a version control system, the requirements.txt generated by this rule should
+ be checked into it to ensure that all developers/users have the same dependency versions.
+
+ Args:
+ name: base name for generated targets, typically "requirements".
+ extra_args: passed to pip-compile.
+ extra_deps: extra dependencies passed to pip-compile.
+ generate_hashes: whether to put hashes in the requirements_txt file.
+ py_binary: the py_binary rule to be used.
+ py_test: the py_test rule to be used.
+ requirements_in: file expressing desired dependencies.
+ requirements_txt: result of "compiling" the requirements.in file.
+ requirements_linux: File of linux specific resolve output to check validate if requirement.in has changes.
+ requirements_darwin: File of darwin specific resolve output to check validate if requirement.in has changes.
+ requirements_windows: File of windows specific resolve output to check validate if requirement.in has changes.
+ tags: tagging attribute common to all build rules, passed to both the _test and .update rules.
+ visibility: passed to both the _test and .update rules.
+ **kwargs: other bazel attributes passed to the "_test" rule.
+ """
+ requirements_in = name + ".in" if requirements_in == None else requirements_in
+ requirements_txt = name + ".txt" if requirements_txt == None else requirements_txt
+
+ # "Default" target produced by this macro
+ # Allow a compile_pip_requirements rule to include another one in the data
+ # for a requirements file that does `-r ../other/requirements.txt`
+ native.filegroup(
+ name = name,
+ srcs = kwargs.pop("data", []) + [requirements_txt],
+ visibility = visibility,
+ )
+
+ data = [name, requirements_in, requirements_txt] + [f for f in (requirements_linux, requirements_darwin, requirements_windows) if f != None]
+
+ # Use the Label constructor so this is expanded in the context of the file
+ # where it appears, which is to say, in @rules_python
+ pip_compile = Label("//python/pip_install/tools/dependency_resolver:dependency_resolver.py")
+
+ loc = "$(rlocationpath {})"
+
+ args = [
+ loc.format(requirements_in),
+ loc.format(requirements_txt),
+ # String None is a placeholder for argv ordering.
+ loc.format(requirements_linux) if requirements_linux else "None",
+ loc.format(requirements_darwin) if requirements_darwin else "None",
+ loc.format(requirements_windows) if requirements_windows else "None",
+ "//%s:%s.update" % (native.package_name(), name),
+ ] + (["--generate-hashes"] if generate_hashes else []) + extra_args
+
+ deps = [
+ requirement("build"),
+ requirement("click"),
+ requirement("colorama"),
+ requirement("pep517"),
+ requirement("pip"),
+ requirement("pip_tools"),
+ requirement("setuptools"),
+ requirement("tomli"),
+ requirement("importlib_metadata"),
+ requirement("zipp"),
+ requirement("more_itertools"),
+ Label("//python/runfiles:runfiles"),
+ ] + extra_deps
+
+ tags = tags or []
+ tags.append("requires-network")
+ tags.append("no-remote-exec")
+ tags.append("no-sandbox")
+ attrs = {
+ "args": args,
+ "data": data,
+ "deps": deps,
+ "main": pip_compile,
+ "srcs": [pip_compile],
+ "tags": tags,
+ "visibility": visibility,
+ }
+
+ # cheap way to detect the bazel version
+ _bazel_version_4_or_greater = "propeller_optimize" in dir(native)
+
+ # Bazel 4.0 added the "env" attribute to py_test/py_binary
+ if _bazel_version_4_or_greater:
+ attrs["env"] = kwargs.pop("env", {})
+
+ py_binary(
+ name = name + ".update",
+ **attrs
+ )
+
+ timeout = kwargs.pop("timeout", "short")
+
+ py_test(
+ name = name + "_test",
+ timeout = timeout,
+ # kwargs could contain test-specific attributes like size or timeout
+ **dict(attrs, **kwargs)
+ )
diff --git a/python/pip_install/requirements_parser.bzl b/python/pip_install/requirements_parser.bzl
new file mode 100644
index 0000000..3b49fdf
--- /dev/null
+++ b/python/pip_install/requirements_parser.bzl
@@ -0,0 +1,133 @@
+# 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.
+
+"Pip requirements parser for Starlark"
+
+_STATE = struct(
+ # Consume extraneous whitespace
+ ConsumeSpace = 0,
+ # Consume a comment
+ ConsumeComment = 1,
+ # Parse the name of a pip package
+ ParseDependency = 2,
+ # Parse a full requirement line
+ ParseRequirement = 3,
+ # Parse a pip option
+ ParseOption = 4,
+)
+
+EOF = {}
+
+def parse(content):
+ """A simplistic (and incomplete) pip requirements lockfile parser.
+
+ Parses package names and their full requirement lines, as well pip
+ options.
+
+ Args:
+ content: lockfile content as a string
+
+ Returns:
+ Struct with fields `requirements` and `options`.
+
+ requirements: List of requirements, where each requirement is a 2-element
+ tuple containing the package name and the requirement line.
+ E.g., [(certifi', 'certifi==2021.10.8 --hash=sha256:7888...'), ...]
+
+ options: List of pip option lines
+ """
+ content = content.replace("\r", "")
+
+ result = struct(
+ requirements = [],
+ options = [],
+ )
+ state = _STATE.ConsumeSpace
+ buffer = ""
+
+ inputs = content.elems()[:]
+ inputs.append(EOF)
+
+ for input in inputs:
+ if state == _STATE.ConsumeSpace:
+ (state, buffer) = _handleConsumeSpace(input)
+ elif state == _STATE.ConsumeComment:
+ (state, buffer) = _handleConsumeComment(input, buffer, result)
+ elif state == _STATE.ParseDependency:
+ (state, buffer) = _handleParseDependency(input, buffer, result)
+ elif state == _STATE.ParseOption:
+ (state, buffer) = _handleParseOption(input, buffer, result)
+ elif state == _STATE.ParseRequirement:
+ (state, buffer) = _handleParseRequirement(input, buffer, result)
+ else:
+ fail("Unknown state %d" % state)
+
+ return result
+
+def _handleConsumeSpace(input):
+ if input == EOF:
+ return (_STATE.ConsumeSpace, "")
+ if input.isspace():
+ return (_STATE.ConsumeSpace, "")
+ elif input == "#":
+ return (_STATE.ConsumeComment, "")
+ elif input == "-":
+ return (_STATE.ParseOption, input)
+
+ return (_STATE.ParseDependency, input)
+
+def _handleConsumeComment(input, buffer, result):
+ if input == "\n":
+ if len(result.requirements) > 0 and len(result.requirements[-1]) == 1:
+ result.requirements[-1] = (result.requirements[-1][0], buffer.rstrip(" \n"))
+ return (_STATE.ConsumeSpace, "")
+ elif len(buffer) > 0:
+ result.options.append(buffer.rstrip(" \n"))
+ return (_STATE.ConsumeSpace, "")
+ return (_STATE.ConsumeSpace, "")
+ return (_STATE.ConsumeComment, buffer)
+
+def _handleParseDependency(input, buffer, result):
+ if input == EOF:
+ fail("Enountered unexpected end of file while parsing requirement")
+ elif input.isspace() or input in [">", "<", "~", "=", ";", "["]:
+ result.requirements.append((buffer,))
+ return (_STATE.ParseRequirement, buffer + input)
+
+ return (_STATE.ParseDependency, buffer + input)
+
+def _handleParseOption(input, buffer, result):
+ if input == "\n" and buffer.endswith("\\"):
+ return (_STATE.ParseOption, buffer[0:-1])
+ elif input == " ":
+ result.options.append(buffer.rstrip("\n"))
+ return (_STATE.ParseOption, "")
+ elif input == "\n" or input == EOF:
+ result.options.append(buffer.rstrip("\n"))
+ return (_STATE.ConsumeSpace, "")
+ elif input == "#" and (len(buffer) == 0 or buffer[-1].isspace()):
+ return (_STATE.ConsumeComment, buffer)
+
+ return (_STATE.ParseOption, buffer + input)
+
+def _handleParseRequirement(input, buffer, result):
+ if input == "\n" and buffer.endswith("\\"):
+ return (_STATE.ParseRequirement, buffer[0:-1])
+ elif input == "\n" or input == EOF:
+ result.requirements[-1] = (result.requirements[-1][0], buffer.rstrip(" \n"))
+ return (_STATE.ConsumeSpace, "")
+ elif input == "#" and (len(buffer) == 0 or buffer[-1].isspace()):
+ return (_STATE.ConsumeComment, buffer)
+
+ return (_STATE.ParseRequirement, buffer + input)
diff --git a/python/pip_install/tools/dependency_resolver/BUILD.bazel b/python/pip_install/tools/dependency_resolver/BUILD.bazel
new file mode 100644
index 0000000..c2cfb39
--- /dev/null
+++ b/python/pip_install/tools/dependency_resolver/BUILD.bazel
@@ -0,0 +1,19 @@
+exports_files(["dependency_resolver.py"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(
+ ["*"],
+ exclude = ["*_test.py"],
+ ),
+ visibility = ["//python/pip_install:__subpackages__"],
+)
+
+filegroup(
+ name = "py_srcs",
+ srcs = glob(
+ include = ["**/*.py"],
+ exclude = ["**/*_test.py"],
+ ),
+ visibility = ["//python/pip_install:__subpackages__"],
+)
diff --git a/python/pip_install/tools/dependency_resolver/__init__.py b/python/pip_install/tools/dependency_resolver/__init__.py
new file mode 100644
index 0000000..bbdfb4c
--- /dev/null
+++ b/python/pip_install/tools/dependency_resolver/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/python/pip_install/tools/dependency_resolver/dependency_resolver.py b/python/pip_install/tools/dependency_resolver/dependency_resolver.py
new file mode 100644
index 0000000..e277cf9
--- /dev/null
+++ b/python/pip_install/tools/dependency_resolver/dependency_resolver.py
@@ -0,0 +1,221 @@
+# 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.
+
+"Set defaults for the pip-compile command to run it under Bazel"
+
+import atexit
+import os
+import shutil
+import sys
+from pathlib import Path
+
+import piptools.writer as piptools_writer
+from piptools.scripts.compile import cli
+
+from python.runfiles import runfiles
+
+# Replace the os.replace function with shutil.copy to work around os.replace not being able to
+# replace or move files across filesystems.
+os.replace = shutil.copy
+
+# Next, we override the annotation_style_split and annotation_style_line functions to replace the
+# backslashes in the paths with forward slashes. This is so that we can have the same requirements
+# file on Windows and Unix-like.
+original_annotation_style_split = piptools_writer.annotation_style_split
+original_annotation_style_line = piptools_writer.annotation_style_line
+
+
+def annotation_style_split(required_by) -> str:
+ required_by = set([v.replace("\\", "/") for v in required_by])
+ return original_annotation_style_split(required_by)
+
+
+def annotation_style_line(required_by) -> str:
+ required_by = set([v.replace("\\", "/") for v in required_by])
+ return original_annotation_style_line(required_by)
+
+
+piptools_writer.annotation_style_split = annotation_style_split
+piptools_writer.annotation_style_line = annotation_style_line
+
+
+def _select_golden_requirements_file(
+ requirements_txt, requirements_linux, requirements_darwin, requirements_windows
+):
+ """Switch the golden requirements file, used to validate if updates are needed,
+ to a specified platform specific one. Fallback on the platform independent one.
+ """
+
+ plat = sys.platform
+ if plat == "linux" and requirements_linux is not None:
+ return requirements_linux
+ elif plat == "darwin" and requirements_darwin is not None:
+ return requirements_darwin
+ elif plat == "win32" and requirements_windows is not None:
+ return requirements_windows
+ else:
+ return requirements_txt
+
+
+def _locate(bazel_runfiles, file):
+ """Look up the file via Rlocation"""
+
+ if not file:
+ return file
+
+ return bazel_runfiles.Rlocation(file)
+
+
+if __name__ == "__main__":
+ if len(sys.argv) < 4:
+ print(
+ "Expected at least two arguments: requirements_in requirements_out",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+
+ parse_str_none = lambda s: None if s == "None" else s
+ bazel_runfiles = runfiles.Create()
+
+ requirements_in = sys.argv.pop(1)
+ requirements_txt = sys.argv.pop(1)
+ requirements_linux = parse_str_none(sys.argv.pop(1))
+ requirements_darwin = parse_str_none(sys.argv.pop(1))
+ requirements_windows = parse_str_none(sys.argv.pop(1))
+ update_target_label = sys.argv.pop(1)
+
+ requirements_file = _select_golden_requirements_file(
+ requirements_txt=requirements_txt, requirements_linux=requirements_linux,
+ requirements_darwin=requirements_darwin, requirements_windows=requirements_windows
+ )
+
+ resolved_requirements_in = _locate(bazel_runfiles, requirements_in)
+ resolved_requirements_file = _locate(bazel_runfiles, requirements_file)
+
+ # Files in the runfiles directory has the following naming schema:
+ # Main repo: __main__/<path_to_file>
+ # External repo: <workspace name>/<path_to_file>
+ # We want to strip both __main__ and <workspace name> from the absolute prefix
+ # to keep the requirements lock file agnostic.
+ repository_prefix = requirements_file[: requirements_file.index("/") + 1]
+ absolute_path_prefix = resolved_requirements_file[
+ : -(len(requirements_file) - len(repository_prefix))
+ ]
+
+ # As requirements_in might contain references to generated files we want to
+ # use the runfiles file first. Thus, we need to compute the relative path
+ # from the execution root.
+ # Note: Windows cannot reference generated files without runfiles support enabled.
+ requirements_in_relative = requirements_in[len(repository_prefix):]
+ requirements_file_relative = requirements_file[len(repository_prefix):]
+
+ # Before loading click, set the locale for its parser.
+ # If it leaks through to the system setting, it may fail:
+ # RuntimeError: Click will abort further execution because Python 3 was configured to use ASCII
+ # as encoding for the environment. Consult https://click.palletsprojects.com/python3/ for
+ # mitigation steps.
+ os.environ["LC_ALL"] = "C.UTF-8"
+ os.environ["LANG"] = "C.UTF-8"
+
+ UPDATE = True
+ # Detect if we are running under `bazel test`.
+ if "TEST_TMPDIR" in os.environ:
+ UPDATE = False
+ # pip-compile wants the cache files to be writeable, but if we point
+ # to the real user cache, Bazel sandboxing makes the file read-only
+ # and we fail.
+ # In theory this makes the test more hermetic as well.
+ sys.argv.append("--cache-dir")
+ sys.argv.append(os.environ["TEST_TMPDIR"])
+ # Make a copy for pip-compile to read and mutate.
+ requirements_out = os.path.join(
+ os.environ["TEST_TMPDIR"], os.path.basename(requirements_file) + ".out"
+ )
+ # Those two files won't necessarily be on the same filesystem, so we can't use os.replace
+ # or shutil.copyfile, as they will fail with OSError: [Errno 18] Invalid cross-device link.
+ shutil.copy(resolved_requirements_file, requirements_out)
+
+ update_command = os.getenv("CUSTOM_COMPILE_COMMAND") or "bazel run %s" % (
+ update_target_label,
+ )
+
+ os.environ["CUSTOM_COMPILE_COMMAND"] = update_command
+ os.environ["PIP_CONFIG_FILE"] = os.getenv("PIP_CONFIG_FILE") or os.devnull
+
+ sys.argv.append("--output-file")
+ sys.argv.append(requirements_file_relative if UPDATE else requirements_out)
+ sys.argv.append(
+ requirements_in_relative
+ if Path(requirements_in_relative).exists()
+ else resolved_requirements_in
+ )
+ print(sys.argv)
+
+ if UPDATE:
+ print("Updating " + requirements_file_relative)
+ if "BUILD_WORKSPACE_DIRECTORY" in os.environ:
+ workspace = os.environ["BUILD_WORKSPACE_DIRECTORY"]
+ requirements_file_tree = os.path.join(workspace, requirements_file_relative)
+ # In most cases, requirements_file will be a symlink to the real file in the source tree.
+ # If symlinks are not enabled (e.g. on Windows), then requirements_file will be a copy,
+ # and we should copy the updated requirements back to the source tree.
+ if not os.path.samefile(resolved_requirements_file, requirements_file_tree):
+ atexit.register(
+ lambda: shutil.copy(
+ resolved_requirements_file, requirements_file_tree
+ )
+ )
+ cli()
+ requirements_file_relative_path = Path(requirements_file_relative)
+ content = requirements_file_relative_path.read_text()
+ content = content.replace(absolute_path_prefix, "")
+ requirements_file_relative_path.write_text(content)
+ else:
+ # cli will exit(0) on success
+ try:
+ print("Checking " + requirements_file)
+ cli()
+ print("cli() should exit", file=sys.stderr)
+ sys.exit(1)
+ except SystemExit as e:
+ if e.code == 2:
+ print(
+ "pip-compile exited with code 2. This means that pip-compile found "
+ "incompatible requirements or could not find a version that matches "
+ f"the install requirement in {requirements_in_relative}.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+ elif e.code == 0:
+ golden = open(_locate(bazel_runfiles, requirements_file)).readlines()
+ out = open(requirements_out).readlines()
+ out = [line.replace(absolute_path_prefix, "") for line in out]
+ if golden != out:
+ import difflib
+
+ print("".join(difflib.unified_diff(golden, out)), file=sys.stderr)
+ print(
+ "Lock file out of date. Run '"
+ + update_command
+ + "' to update.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
+ sys.exit(0)
+ else:
+ print(
+ f"pip-compile unexpectedly exited with code {e.code}.",
+ file=sys.stderr,
+ )
+ sys.exit(1)
diff --git a/python/pip_install/tools/lib/BUILD.bazel b/python/pip_install/tools/lib/BUILD.bazel
new file mode 100644
index 0000000..37a8b09
--- /dev/null
+++ b/python/pip_install/tools/lib/BUILD.bazel
@@ -0,0 +1,82 @@
+load("//python:defs.bzl", "py_library", "py_test")
+load(":annotations_test_helpers.bzl", "package_annotation", "package_annotations_file")
+
+py_library(
+ name = "lib",
+ srcs = [
+ "annotation.py",
+ "arguments.py",
+ "bazel.py",
+ ],
+ visibility = ["//python/pip_install:__subpackages__"],
+)
+
+package_annotations_file(
+ name = "mock_annotations",
+ annotations = {
+ "pkg_a": package_annotation(),
+ "pkg_b": package_annotation(
+ data_exclude_glob = [
+ "*.foo",
+ "*.bar",
+ ],
+ ),
+ "pkg_c": package_annotation(
+ # The `join` and `strip` here accounts for potential differences
+ # in new lines between unix and windows hosts.
+ additive_build_content = "\n".join([line.strip() for line in """\
+cc_library(
+ name = "my_target",
+ hdrs = glob(["**/*.h"]),
+ srcs = glob(["**/*.cc"]),
+)
+""".splitlines()]),
+ data = [":my_target"],
+ ),
+ "pkg_d": package_annotation(
+ srcs_exclude_glob = ["pkg_d/tests/**"],
+ ),
+ },
+ tags = ["manual"],
+)
+
+py_test(
+ name = "annotations_test",
+ size = "small",
+ srcs = ["annotations_test.py"],
+ data = [":mock_annotations"],
+ env = {"MOCK_ANNOTATIONS": "$(rootpath :mock_annotations)"},
+ deps = [
+ ":lib",
+ "//python/runfiles",
+ ],
+)
+
+py_test(
+ name = "arguments_test",
+ size = "small",
+ srcs = [
+ "arguments_test.py",
+ ],
+ deps = [
+ ":lib",
+ ],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(
+ ["*"],
+ exclude = ["*_test.py"],
+ ),
+ visibility = ["//python/pip_install:__subpackages__"],
+)
+
+filegroup(
+ name = "py_srcs",
+ srcs = glob(
+ include = ["**/*.py"],
+ exclude = ["**/*_test.py"],
+ ),
+ visibility = ["//python/pip_install:__subpackages__"],
+)
diff --git a/python/pip_install/tools/lib/__init__.py b/python/pip_install/tools/lib/__init__.py
new file mode 100644
index 0000000..bbdfb4c
--- /dev/null
+++ b/python/pip_install/tools/lib/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/python/pip_install/tools/lib/annotation.py b/python/pip_install/tools/lib/annotation.py
new file mode 100644
index 0000000..c980080
--- /dev/null
+++ b/python/pip_install/tools/lib/annotation.py
@@ -0,0 +1,129 @@
+# 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.
+
+import json
+import logging
+from collections import OrderedDict
+from pathlib import Path
+from typing import Any, Dict, List
+
+
+class Annotation(OrderedDict):
+ """A python representation of `@rules_python//python:pip.bzl%package_annotation`"""
+
+ def __init__(self, content: Dict[str, Any]) -> None:
+
+ missing = []
+ ordered_content = OrderedDict()
+ for field in (
+ "additive_build_content",
+ "copy_executables",
+ "copy_files",
+ "data",
+ "data_exclude_glob",
+ "srcs_exclude_glob",
+ ):
+ if field not in content:
+ missing.append(field)
+ continue
+ ordered_content.update({field: content.pop(field)})
+
+ if missing:
+ raise ValueError("Data missing from initial annotation: {}".format(missing))
+
+ if content:
+ raise ValueError(
+ "Unexpected data passed to annotations: {}".format(
+ sorted(list(content.keys()))
+ )
+ )
+
+ return OrderedDict.__init__(self, ordered_content)
+
+ @property
+ def additive_build_content(self) -> str:
+ return self["additive_build_content"]
+
+ @property
+ def copy_executables(self) -> Dict[str, str]:
+ return self["copy_executables"]
+
+ @property
+ def copy_files(self) -> Dict[str, str]:
+ return self["copy_files"]
+
+ @property
+ def data(self) -> List[str]:
+ return self["data"]
+
+ @property
+ def data_exclude_glob(self) -> List[str]:
+ return self["data_exclude_glob"]
+
+ @property
+ def srcs_exclude_glob(self) -> List[str]:
+ return self["srcs_exclude_glob"]
+
+
+class AnnotationsMap:
+ """A mapping of python package names to [Annotation]"""
+
+ def __init__(self, json_file: Path):
+ content = json.loads(json_file.read_text())
+
+ self._annotations = {pkg: Annotation(data) for (pkg, data) in content.items()}
+
+ @property
+ def annotations(self) -> Dict[str, Annotation]:
+ return self._annotations
+
+ def collect(self, requirements: List[str]) -> Dict[str, Annotation]:
+ unused = self.annotations
+ collection = {}
+ for pkg in requirements:
+ if pkg in unused:
+ collection.update({pkg: unused.pop(pkg)})
+
+ if unused:
+ logging.warning(
+ "Unused annotations: {}".format(sorted(list(unused.keys())))
+ )
+
+ return collection
+
+
+def annotation_from_str_path(path: str) -> Annotation:
+ """Load an annotation from a json encoded file
+
+ Args:
+ path (str): The path to a json encoded file
+
+ Returns:
+ Annotation: The deserialized annotations
+ """
+ json_file = Path(path)
+ content = json.loads(json_file.read_text())
+ return Annotation(content)
+
+
+def annotations_map_from_str_path(path: str) -> AnnotationsMap:
+ """Load an annotations map from a json encoded file
+
+ Args:
+ path (str): The path to a json encoded file
+
+ Returns:
+ AnnotationsMap: The deserialized annotations map
+ """
+ return AnnotationsMap(Path(path))
diff --git a/python/pip_install/tools/lib/annotations_test.py b/python/pip_install/tools/lib/annotations_test.py
new file mode 100644
index 0000000..f7c360f
--- /dev/null
+++ b/python/pip_install/tools/lib/annotations_test.py
@@ -0,0 +1,121 @@
+#!/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.
+
+
+import os
+import textwrap
+import unittest
+from pathlib import Path
+
+from python.pip_install.tools.lib.annotation import Annotation, AnnotationsMap
+from python.runfiles import runfiles
+
+
+class AnnotationsTestCase(unittest.TestCase):
+
+ maxDiff = None
+
+ def test_annotations_constructor(self) -> None:
+ annotations_env = os.environ.get("MOCK_ANNOTATIONS")
+ self.assertIsNotNone(annotations_env)
+
+ r = runfiles.Create()
+
+ annotations_path = Path(r.Rlocation("rules_python/{}".format(annotations_env)))
+ self.assertTrue(annotations_path.exists())
+
+ annotations_map = AnnotationsMap(annotations_path)
+ self.assertListEqual(
+ list(annotations_map.annotations.keys()),
+ ["pkg_a", "pkg_b", "pkg_c", "pkg_d"],
+ )
+
+ collection = annotations_map.collect(["pkg_a", "pkg_b", "pkg_c", "pkg_d"])
+
+ self.assertEqual(
+ collection["pkg_a"],
+ Annotation(
+ {
+ "additive_build_content": None,
+ "copy_executables": {},
+ "copy_files": {},
+ "data": [],
+ "data_exclude_glob": [],
+ "srcs_exclude_glob": [],
+ }
+ ),
+ )
+
+ self.assertEqual(
+ collection["pkg_b"],
+ Annotation(
+ {
+ "additive_build_content": None,
+ "copy_executables": {},
+ "copy_files": {},
+ "data": [],
+ "data_exclude_glob": ["*.foo", "*.bar"],
+ "srcs_exclude_glob": [],
+ }
+ ),
+ )
+
+ self.assertEqual(
+ collection["pkg_c"],
+ Annotation(
+ {
+ # The `join` and `strip` here accounts for potential
+ # differences in new lines between unix and windows
+ # hosts.
+ "additive_build_content": "\n".join(
+ [
+ line.strip()
+ for line in textwrap.dedent(
+ """\
+ cc_library(
+ name = "my_target",
+ hdrs = glob(["**/*.h"]),
+ srcs = glob(["**/*.cc"]),
+ )
+ """
+ ).splitlines()
+ ]
+ ),
+ "copy_executables": {},
+ "copy_files": {},
+ "data": [":my_target"],
+ "data_exclude_glob": [],
+ "srcs_exclude_glob": [],
+ }
+ ),
+ )
+
+ self.assertEqual(
+ collection["pkg_d"],
+ Annotation(
+ {
+ "additive_build_content": None,
+ "copy_executables": {},
+ "copy_files": {},
+ "data": [],
+ "data_exclude_glob": [],
+ "srcs_exclude_glob": ["pkg_d/tests/**"],
+ }
+ ),
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/python/pip_install/tools/lib/annotations_test_helpers.bzl b/python/pip_install/tools/lib/annotations_test_helpers.bzl
new file mode 100644
index 0000000..4f56bb7
--- /dev/null
+++ b/python/pip_install/tools/lib/annotations_test_helpers.bzl
@@ -0,0 +1,47 @@
+# 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 macros and rules for testing the `annotations` module of `tools`"""
+
+load("//python:pip.bzl", _package_annotation = "package_annotation")
+
+package_annotation = _package_annotation
+
+def _package_annotations_file_impl(ctx):
+ output = ctx.actions.declare_file(ctx.label.name + ".annotations.json")
+
+ annotations = {package: json.decode(data) for (package, data) in ctx.attr.annotations.items()}
+ ctx.actions.write(
+ output = output,
+ content = json.encode_indent(annotations, indent = " " * 4),
+ )
+
+ return DefaultInfo(
+ files = depset([output]),
+ runfiles = ctx.runfiles(files = [output]),
+ )
+
+package_annotations_file = rule(
+ implementation = _package_annotations_file_impl,
+ doc = (
+ "Consumes `package_annotation` definitions in the same way " +
+ "`pip_repository` rules do to produce an annotations file."
+ ),
+ attrs = {
+ "annotations": attr.string_dict(
+ doc = "See `@rules_python//python:pip.bzl%package_annotation",
+ mandatory = True,
+ ),
+ },
+)
diff --git a/python/pip_install/tools/lib/arguments.py b/python/pip_install/tools/lib/arguments.py
new file mode 100644
index 0000000..974f03c
--- /dev/null
+++ b/python/pip_install/tools/lib/arguments.py
@@ -0,0 +1,76 @@
+# 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.
+
+import json
+from argparse import ArgumentParser
+
+
+def parse_common_args(parser: ArgumentParser) -> ArgumentParser:
+ parser.add_argument(
+ "--repo",
+ action="store",
+ required=True,
+ help="The external repo name to install dependencies. In the format '@{REPO_NAME}'",
+ )
+ parser.add_argument(
+ "--isolated",
+ action="store_true",
+ help="Whether or not to include the `--isolated` pip flag.",
+ )
+ parser.add_argument(
+ "--extra_pip_args",
+ action="store",
+ help="Extra arguments to pass down to pip.",
+ )
+ parser.add_argument(
+ "--pip_data_exclude",
+ action="store",
+ help="Additional data exclusion parameters to add to the pip packages BUILD file.",
+ )
+ parser.add_argument(
+ "--enable_implicit_namespace_pkgs",
+ action="store_true",
+ help="Disables conversion of implicit namespace packages into pkg-util style packages.",
+ )
+ parser.add_argument(
+ "--environment",
+ action="store",
+ help="Extra environment variables to set on the pip environment.",
+ )
+ parser.add_argument(
+ "--repo-prefix",
+ required=True,
+ help="Prefix to prepend to packages",
+ )
+ parser.add_argument(
+ "--download_only",
+ action="store_true",
+ help="Use 'pip download' instead of 'pip wheel'. Disables building wheels from source, but allows use of "
+ "--platform, --python-version, --implementation, and --abi in --extra_pip_args.",
+ )
+ return parser
+
+
+def deserialize_structured_args(args):
+ """Deserialize structured arguments passed from the starlark rules.
+ Args:
+ args: dict of parsed command line arguments
+ """
+ structured_args = ("extra_pip_args", "pip_data_exclude", "environment")
+ for arg_name in structured_args:
+ if args.get(arg_name) is not None:
+ args[arg_name] = json.loads(args[arg_name])["arg"]
+ else:
+ args[arg_name] = []
+ return args
diff --git a/python/pip_install/tools/lib/arguments_test.py b/python/pip_install/tools/lib/arguments_test.py
new file mode 100644
index 0000000..dfa96a8
--- /dev/null
+++ b/python/pip_install/tools/lib/arguments_test.py
@@ -0,0 +1,62 @@
+# 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.
+
+import argparse
+import json
+import unittest
+
+from python.pip_install.tools.lib import arguments
+
+
+class ArgumentsTestCase(unittest.TestCase):
+ def test_arguments(self) -> None:
+ parser = argparse.ArgumentParser()
+ parser = arguments.parse_common_args(parser)
+ repo_name = "foo"
+ repo_prefix = "pypi_"
+ index_url = "--index_url=pypi.org/simple"
+ extra_pip_args = [index_url]
+ args_dict = vars(
+ parser.parse_args(
+ args=[
+ "--repo",
+ repo_name,
+ f"--extra_pip_args={json.dumps({'arg': extra_pip_args})}",
+ "--repo-prefix",
+ repo_prefix,
+ ]
+ )
+ )
+ args_dict = arguments.deserialize_structured_args(args_dict)
+ self.assertIn("repo", args_dict)
+ self.assertIn("extra_pip_args", args_dict)
+ self.assertEqual(args_dict["pip_data_exclude"], [])
+ self.assertEqual(args_dict["enable_implicit_namespace_pkgs"], False)
+ self.assertEqual(args_dict["repo"], repo_name)
+ self.assertEqual(args_dict["repo_prefix"], repo_prefix)
+ self.assertEqual(args_dict["extra_pip_args"], extra_pip_args)
+
+ def test_deserialize_structured_args(self) -> None:
+ serialized_args = {
+ "pip_data_exclude": json.dumps({"arg": ["**.foo"]}),
+ "environment": json.dumps({"arg": {"PIP_DO_SOMETHING": "True"}}),
+ }
+ args = arguments.deserialize_structured_args(serialized_args)
+ self.assertEqual(args["pip_data_exclude"], ["**.foo"])
+ self.assertEqual(args["environment"], {"PIP_DO_SOMETHING": "True"})
+ self.assertEqual(args["extra_pip_args"], [])
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/python/pip_install/tools/lib/bazel.py b/python/pip_install/tools/lib/bazel.py
new file mode 100644
index 0000000..81119e9
--- /dev/null
+++ b/python/pip_install/tools/lib/bazel.py
@@ -0,0 +1,45 @@
+# 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.
+
+import re
+
+WHEEL_FILE_LABEL = "whl"
+PY_LIBRARY_LABEL = "pkg"
+DATA_LABEL = "data"
+DIST_INFO_LABEL = "dist_info"
+WHEEL_ENTRY_POINT_PREFIX = "rules_python_wheel_entry_point"
+
+
+def sanitise_name(name: str, prefix: str) -> str:
+ """Sanitises the name to be compatible with Bazel labels.
+
+ See the doc in ../../../private/normalize_name.bzl.
+ """
+ return prefix + re.sub(r"[-_.]+", "_", name).lower()
+
+
+def _whl_name_to_repo_root(whl_name: str, repo_prefix: str) -> str:
+ return "@{}//".format(sanitise_name(whl_name, prefix=repo_prefix))
+
+
+def sanitised_repo_library_label(whl_name: str, repo_prefix: str) -> str:
+ return '"{}:{}"'.format(
+ _whl_name_to_repo_root(whl_name, repo_prefix), PY_LIBRARY_LABEL
+ )
+
+
+def sanitised_repo_file_label(whl_name: str, repo_prefix: str) -> str:
+ return '"{}:{}"'.format(
+ _whl_name_to_repo_root(whl_name, repo_prefix), WHEEL_FILE_LABEL
+ )
diff --git a/python/pip_install/tools/wheel_installer/BUILD.bazel b/python/pip_install/tools/wheel_installer/BUILD.bazel
new file mode 100644
index 0000000..54bbc46
--- /dev/null
+++ b/python/pip_install/tools/wheel_installer/BUILD.bazel
@@ -0,0 +1,66 @@
+load("//python:defs.bzl", "py_binary", "py_library", "py_test")
+load("//python/pip_install:repositories.bzl", "requirement")
+
+py_library(
+ name = "lib",
+ srcs = [
+ "namespace_pkgs.py",
+ "wheel.py",
+ "wheel_installer.py",
+ ],
+ deps = [
+ "//python/pip_install/tools/lib",
+ requirement("installer"),
+ requirement("pip"),
+ requirement("setuptools"),
+ ],
+)
+
+py_binary(
+ name = "wheel_installer",
+ srcs = [
+ "wheel_installer.py",
+ ],
+ deps = [":lib"],
+)
+
+py_test(
+ name = "namespace_pkgs_test",
+ size = "small",
+ srcs = [
+ "namespace_pkgs_test.py",
+ ],
+ deps = [
+ ":lib",
+ ],
+)
+
+py_test(
+ name = "wheel_installer_test",
+ size = "small",
+ srcs = [
+ "wheel_installer_test.py",
+ ],
+ data = ["//examples/wheel:minimal_with_py_package"],
+ deps = [
+ ":lib",
+ ],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(
+ ["*"],
+ exclude = ["*_test.py"],
+ ),
+ visibility = ["//python/pip_install:__subpackages__"],
+)
+
+filegroup(
+ name = "py_srcs",
+ srcs = glob(
+ include = ["**/*.py"],
+ exclude = ["**/*_test.py"],
+ ),
+ visibility = ["//python/pip_install:__subpackages__"],
+)
diff --git a/python/pip_install/tools/wheel_installer/namespace_pkgs.py b/python/pip_install/tools/wheel_installer/namespace_pkgs.py
new file mode 100644
index 0000000..7d23c0e
--- /dev/null
+++ b/python/pip_install/tools/wheel_installer/namespace_pkgs.py
@@ -0,0 +1,121 @@
+# 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.
+
+"""Utility functions to discover python package types"""
+import os
+import textwrap
+from pathlib import Path # supported in >= 3.4
+from typing import List, Optional, Set
+
+
+def implicit_namespace_packages(
+ directory: str, ignored_dirnames: Optional[List[str]] = None
+) -> Set[Path]:
+ """Discovers namespace packages implemented using the 'native namespace packages' method.
+
+ AKA 'implicit namespace packages', which has been supported since Python 3.3.
+ See: https://packaging.python.org/guides/packaging-namespace-packages/#native-namespace-packages
+
+ Args:
+ directory: The root directory to recursively find packages in.
+ ignored_dirnames: A list of directories to exclude from the search
+
+ Returns:
+ The set of directories found under root to be packages using the native namespace method.
+ """
+ namespace_pkg_dirs: Set[Path] = set()
+ standard_pkg_dirs: Set[Path] = set()
+ directory_path = Path(directory)
+ ignored_dirname_paths: List[Path] = [Path(p) for p in ignored_dirnames or ()]
+ # Traverse bottom-up because a directory can be a namespace pkg because its child contains module files.
+ for dirpath, dirnames, filenames in map(
+ lambda t: (Path(t[0]), *t[1:]), os.walk(directory_path, topdown=False)
+ ):
+ if "__init__.py" in filenames:
+ standard_pkg_dirs.add(dirpath)
+ continue
+ elif ignored_dirname_paths:
+ is_ignored_dir = dirpath in ignored_dirname_paths
+ child_of_ignored_dir = any(
+ d in dirpath.parents for d in ignored_dirname_paths
+ )
+ if is_ignored_dir or child_of_ignored_dir:
+ continue
+
+ dir_includes_py_modules = _includes_python_modules(filenames)
+ parent_of_namespace_pkg = any(
+ Path(dirpath, d) in namespace_pkg_dirs for d in dirnames
+ )
+ parent_of_standard_pkg = any(
+ Path(dirpath, d) in standard_pkg_dirs for d in dirnames
+ )
+ parent_of_pkg = parent_of_namespace_pkg or parent_of_standard_pkg
+ if (
+ (dir_includes_py_modules or parent_of_pkg)
+ and
+ # The root of the directory should never be an implicit namespace
+ dirpath != directory_path
+ ):
+ namespace_pkg_dirs.add(dirpath)
+ return namespace_pkg_dirs
+
+
+def add_pkgutil_style_namespace_pkg_init(dir_path: Path) -> None:
+ """Adds 'pkgutil-style namespace packages' init file to the given directory
+
+ See: https://packaging.python.org/guides/packaging-namespace-packages/#pkgutil-style-namespace-packages
+
+ Args:
+ dir_path: The directory to create an __init__.py for.
+
+ Raises:
+ ValueError: If the directory already contains an __init__.py file
+ """
+ ns_pkg_init_filepath = os.path.join(dir_path, "__init__.py")
+
+ if os.path.isfile(ns_pkg_init_filepath):
+ raise ValueError("%s already contains an __init__.py file." % dir_path)
+
+ with open(ns_pkg_init_filepath, "w") as ns_pkg_init_f:
+ # See https://packaging.python.org/guides/packaging-namespace-packages/#pkgutil-style-namespace-packages
+ ns_pkg_init_f.write(
+ textwrap.dedent(
+ """\
+ # __path__ manipulation added by bazelbuild/rules_python to support namespace pkgs.
+ __path__ = __import__('pkgutil').extend_path(__path__, __name__)
+ """
+ )
+ )
+
+
+def _includes_python_modules(files: List[str]) -> bool:
+ """
+ In order to only transform directories that Python actually considers namespace pkgs
+ we need to detect if a directory includes Python modules.
+
+ Which files are loadable as modules is extension based, and the particular set of extensions
+ varies by platform.
+
+ See:
+ 1. https://github.com/python/cpython/blob/7d9d25dbedfffce61fc76bc7ccbfa9ae901bf56f/Lib/importlib/machinery.py#L19
+ 2. PEP 420 -- Implicit Namespace Packages, Specification - https://www.python.org/dev/peps/pep-0420/#specification
+ 3. dynload_shlib.c and dynload_win.c in python/cpython.
+ """
+ module_suffixes = {
+ ".py", # Source modules
+ ".pyc", # Compiled bytecode modules
+ ".so", # Unix extension modules
+ ".pyd", # https://docs.python.org/3/faq/windows.html#is-a-pyd-file-the-same-as-a-dll
+ }
+ return any(Path(f).suffix in module_suffixes for f in files)
diff --git a/python/pip_install/tools/wheel_installer/namespace_pkgs_test.py b/python/pip_install/tools/wheel_installer/namespace_pkgs_test.py
new file mode 100644
index 0000000..4aa0fea
--- /dev/null
+++ b/python/pip_install/tools/wheel_installer/namespace_pkgs_test.py
@@ -0,0 +1,192 @@
+# 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.
+
+import os
+import pathlib
+import shutil
+import tempfile
+import unittest
+from typing import Optional, Set
+
+from python.pip_install.tools.wheel_installer import namespace_pkgs
+
+
+class TempDir:
+ def __init__(self) -> None:
+ self.dir = tempfile.mkdtemp()
+
+ def root(self) -> str:
+ return self.dir
+
+ def add_dir(self, rel_path: str) -> None:
+ d = pathlib.Path(self.dir, rel_path)
+ d.mkdir(parents=True)
+
+ def add_file(self, rel_path: str, contents: Optional[str] = None) -> None:
+ f = pathlib.Path(self.dir, rel_path)
+ f.parent.mkdir(parents=True, exist_ok=True)
+ if contents:
+ with open(str(f), "w") as writeable_f:
+ writeable_f.write(contents)
+ else:
+ f.touch()
+
+ def remove(self) -> None:
+ shutil.rmtree(self.dir)
+
+
+class TestImplicitNamespacePackages(unittest.TestCase):
+ def assertPathsEqual(self, actual: Set[pathlib.Path], expected: Set[str]) -> None:
+ self.assertEqual(actual, {pathlib.Path(p) for p in expected})
+
+ def test_in_current_directory(self) -> None:
+ directory = TempDir()
+ directory.add_file("foo/bar/biz.py")
+ directory.add_file("foo/bee/boo.py")
+ directory.add_file("foo/buu/__init__.py")
+ directory.add_file("foo/buu/bii.py")
+ cwd = os.getcwd()
+ os.chdir(directory.root())
+ expected = {
+ "foo",
+ "foo/bar",
+ "foo/bee",
+ }
+ try:
+ actual = namespace_pkgs.implicit_namespace_packages(".")
+ self.assertPathsEqual(actual, expected)
+ finally:
+ os.chdir(cwd)
+ directory.remove()
+
+ def test_finds_correct_namespace_packages(self) -> None:
+ directory = TempDir()
+ directory.add_file("foo/bar/biz.py")
+ directory.add_file("foo/bee/boo.py")
+ directory.add_file("foo/buu/__init__.py")
+ directory.add_file("foo/buu/bii.py")
+
+ expected = {
+ directory.root() + "/foo",
+ directory.root() + "/foo/bar",
+ directory.root() + "/foo/bee",
+ }
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertPathsEqual(actual, expected)
+
+ def test_ignores_empty_directories(self) -> None:
+ directory = TempDir()
+ directory.add_file("foo/bar/biz.py")
+ directory.add_dir("foo/cat")
+
+ expected = {
+ directory.root() + "/foo",
+ directory.root() + "/foo/bar",
+ }
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertPathsEqual(actual, expected)
+
+ def test_empty_case(self) -> None:
+ directory = TempDir()
+ directory.add_file("foo/__init__.py")
+ directory.add_file("foo/bar/__init__.py")
+ directory.add_file("foo/bar/biz.py")
+
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertEqual(actual, set())
+
+ def test_ignores_non_module_files_in_directories(self) -> None:
+ directory = TempDir()
+ directory.add_file("foo/__init__.pyi")
+ directory.add_file("foo/py.typed")
+
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertEqual(actual, set())
+
+ def test_parent_child_relationship_of_namespace_pkgs(self):
+ directory = TempDir()
+ directory.add_file("foo/bar/biff/my_module.py")
+ directory.add_file("foo/bar/biff/another_module.py")
+
+ expected = {
+ directory.root() + "/foo",
+ directory.root() + "/foo/bar",
+ directory.root() + "/foo/bar/biff",
+ }
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertPathsEqual(actual, expected)
+
+ def test_parent_child_relationship_of_namespace_and_standard_pkgs(self):
+ directory = TempDir()
+ directory.add_file("foo/bar/biff/__init__.py")
+ directory.add_file("foo/bar/biff/another_module.py")
+
+ expected = {
+ directory.root() + "/foo",
+ directory.root() + "/foo/bar",
+ }
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertPathsEqual(actual, expected)
+
+ def test_parent_child_relationship_of_namespace_and_nested_standard_pkgs(self):
+ directory = TempDir()
+ directory.add_file("foo/bar/__init__.py")
+ directory.add_file("foo/bar/biff/another_module.py")
+ directory.add_file("foo/bar/biff/__init__.py")
+ directory.add_file("foo/bar/boof/big_module.py")
+ directory.add_file("foo/bar/boof/__init__.py")
+ directory.add_file("fim/in_a_ns_pkg.py")
+
+ expected = {
+ directory.root() + "/foo",
+ directory.root() + "/fim",
+ }
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertPathsEqual(actual, expected)
+
+ def test_recognized_all_nonstandard_module_types(self):
+ directory = TempDir()
+ directory.add_file("ayy/my_module.pyc")
+ directory.add_file("bee/ccc/dee/eee.so")
+ directory.add_file("eff/jee/aych.pyd")
+
+ expected = {
+ directory.root() + "/ayy",
+ directory.root() + "/bee",
+ directory.root() + "/bee/ccc",
+ directory.root() + "/bee/ccc/dee",
+ directory.root() + "/eff",
+ directory.root() + "/eff/jee",
+ }
+ actual = namespace_pkgs.implicit_namespace_packages(directory.root())
+ self.assertPathsEqual(actual, expected)
+
+ def test_skips_ignored_directories(self):
+ directory = TempDir()
+ directory.add_file("foo/boo/my_module.py")
+ directory.add_file("foo/bar/another_module.py")
+
+ expected = {
+ directory.root() + "/foo",
+ directory.root() + "/foo/bar",
+ }
+ actual = namespace_pkgs.implicit_namespace_packages(
+ directory.root(),
+ ignored_dirnames=[directory.root() + "/foo/boo"],
+ )
+ self.assertPathsEqual(actual, expected)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/python/pip_install/tools/wheel_installer/wheel.py b/python/pip_install/tools/wheel_installer/wheel.py
new file mode 100644
index 0000000..84af04c
--- /dev/null
+++ b/python/pip_install/tools/wheel_installer/wheel.py
@@ -0,0 +1,111 @@
+# 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.
+
+"""Utility class to inspect an extracted wheel directory"""
+import email
+from typing import Dict, Optional, Set, Tuple
+
+import installer
+import pkg_resources
+from pip._vendor.packaging.utils import canonicalize_name
+
+
+class Wheel:
+ """Representation of the compressed .whl file"""
+
+ def __init__(self, path: str):
+ self._path = path
+
+ @property
+ def path(self) -> str:
+ return self._path
+
+ @property
+ def name(self) -> str:
+ # TODO Also available as installer.sources.WheelSource.distribution
+ name = str(self.metadata["Name"])
+ return canonicalize_name(name)
+
+ @property
+ def metadata(self) -> email.message.Message:
+ with installer.sources.WheelFile.open(self.path) as wheel_source:
+ metadata_contents = wheel_source.read_dist_info("METADATA")
+ metadata = installer.utils.parse_metadata_file(metadata_contents)
+ return metadata
+
+ @property
+ def version(self) -> str:
+ # TODO Also available as installer.sources.WheelSource.version
+ return str(self.metadata["Version"])
+
+ def entry_points(self) -> Dict[str, Tuple[str, str]]:
+ """Returns the entrypoints defined in the current wheel
+
+ See https://packaging.python.org/specifications/entry-points/ for more info
+
+ Returns:
+ Dict[str, Tuple[str, str]]: A mapping of the entry point's name to it's module and attribute
+ """
+ with installer.sources.WheelFile.open(self.path) as wheel_source:
+ if "entry_points.txt" not in wheel_source.dist_info_filenames:
+ return dict()
+
+ entry_points_mapping = dict()
+ entry_points_contents = wheel_source.read_dist_info("entry_points.txt")
+ entry_points = installer.utils.parse_entrypoints(entry_points_contents)
+ for script, module, attribute, script_section in entry_points:
+ if script_section == "console":
+ entry_points_mapping[script] = (module, attribute)
+
+ return entry_points_mapping
+
+ def dependencies(self, extras_requested: Optional[Set[str]] = None) -> Set[str]:
+ dependency_set = set()
+
+ for wheel_req in self.metadata.get_all("Requires-Dist", []):
+ req = pkg_resources.Requirement(wheel_req) # type: ignore
+
+ if req.marker is None or any(
+ req.marker.evaluate({"extra": extra})
+ for extra in extras_requested or [""]
+ ):
+ dependency_set.add(req.name) # type: ignore
+
+ return dependency_set
+
+ def unzip(self, directory: str) -> None:
+ installation_schemes = {
+ "purelib": "/site-packages",
+ "platlib": "/site-packages",
+ "headers": "/include",
+ "scripts": "/bin",
+ "data": "/data",
+ }
+ destination = installer.destinations.SchemeDictionaryDestination(
+ installation_schemes,
+ # TODO Should entry_point scripts also be handled by installer rather than custom code?
+ interpreter="/dev/null",
+ script_kind="posix",
+ destdir=directory,
+ bytecode_optimization_levels=[],
+ )
+
+ with installer.sources.WheelFile.open(self.path) as wheel_source:
+ installer.install(
+ source=wheel_source,
+ destination=destination,
+ additional_metadata={
+ "INSTALLER": b"https://github.com/bazelbuild/rules_python",
+ },
+ )
diff --git a/python/pip_install/tools/wheel_installer/wheel_installer.py b/python/pip_install/tools/wheel_installer/wheel_installer.py
new file mode 100644
index 0000000..9b363c3
--- /dev/null
+++ b/python/pip_install/tools/wheel_installer/wheel_installer.py
@@ -0,0 +1,452 @@
+# 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.
+
+import argparse
+import errno
+import glob
+import json
+import os
+import re
+import shutil
+import subprocess
+import sys
+import textwrap
+from pathlib import Path
+from tempfile import NamedTemporaryFile
+from typing import Dict, Iterable, List, Optional, Set, Tuple
+
+from pip._vendor.packaging.utils import canonicalize_name
+
+from python.pip_install.tools.lib import annotation, arguments, bazel
+from python.pip_install.tools.wheel_installer import namespace_pkgs, wheel
+
+
+def _configure_reproducible_wheels() -> None:
+ """Modifies the environment to make wheel building reproducible.
+ Wheels created from sdists are not reproducible by default. We can however workaround this by
+ patching in some configuration with environment variables.
+ """
+
+ # wheel, by default, enables debug symbols in GCC. This incidentally captures the build path in the .so file
+ # We can override this behavior by disabling debug symbols entirely.
+ # https://github.com/pypa/pip/issues/6505
+ if "CFLAGS" in os.environ:
+ os.environ["CFLAGS"] += " -g0"
+ else:
+ os.environ["CFLAGS"] = "-g0"
+
+ # set SOURCE_DATE_EPOCH to 1980 so that we can use python wheels
+ # https://github.com/NixOS/nixpkgs/blob/master/doc/languages-frameworks/python.section.md#python-setuppy-bdist_wheel-cannot-create-whl
+ if "SOURCE_DATE_EPOCH" not in os.environ:
+ os.environ["SOURCE_DATE_EPOCH"] = "315532800"
+
+ # Python wheel metadata files can be unstable.
+ # See https://bitbucket.org/pypa/wheel/pull-requests/74/make-the-output-of-metadata-files/diff
+ if "PYTHONHASHSEED" not in os.environ:
+ os.environ["PYTHONHASHSEED"] = "0"
+
+
+def _parse_requirement_for_extra(
+ requirement: str,
+) -> Tuple[Optional[str], Optional[Set[str]]]:
+ """Given a requirement string, returns the requirement name and set of extras, if extras specified.
+ Else, returns (None, None)
+ """
+
+ # https://www.python.org/dev/peps/pep-0508/#grammar
+ extras_pattern = re.compile(
+ r"^\s*([0-9A-Za-z][0-9A-Za-z_.\-]*)\s*\[\s*([0-9A-Za-z][0-9A-Za-z_.\-]*(?:\s*,\s*[0-9A-Za-z][0-9A-Za-z_.\-]*)*)\s*\]"
+ )
+
+ matches = extras_pattern.match(requirement)
+ if matches:
+ return (
+ canonicalize_name(matches.group(1)),
+ {extra.strip() for extra in matches.group(2).split(",")},
+ )
+
+ return None, None
+
+
+def _setup_namespace_pkg_compatibility(wheel_dir: str) -> None:
+ """Converts native namespace packages to pkgutil-style packages
+
+ Namespace packages can be created in one of three ways. They are detailed here:
+ https://packaging.python.org/guides/packaging-namespace-packages/#creating-a-namespace-package
+
+ 'pkgutil-style namespace packages' (2) and 'pkg_resources-style namespace packages' (3) works in Bazel, but
+ 'native namespace packages' (1) do not.
+
+ We ensure compatibility with Bazel of method 1 by converting them into method 2.
+
+ Args:
+ wheel_dir: the directory of the wheel to convert
+ """
+
+ namespace_pkg_dirs = namespace_pkgs.implicit_namespace_packages(
+ wheel_dir,
+ ignored_dirnames=["%s/bin" % wheel_dir],
+ )
+
+ for ns_pkg_dir in namespace_pkg_dirs:
+ namespace_pkgs.add_pkgutil_style_namespace_pkg_init(ns_pkg_dir)
+
+
+def _generate_entry_point_contents(
+ module: str, attribute: str, shebang: str = "#!/usr/bin/env python3"
+) -> str:
+ """Generate the contents of an entry point script.
+
+ Args:
+ module (str): The name of the module to use.
+ attribute (str): The name of the attribute to call.
+ shebang (str, optional): The shebang to use for the entry point python
+ file.
+
+ Returns:
+ str: A string of python code.
+ """
+ return textwrap.dedent(
+ """\
+ {shebang}
+ import sys
+ from {module} import {attribute}
+ if __name__ == "__main__":
+ sys.exit({attribute}())
+ """.format(
+ shebang=shebang, module=module, attribute=attribute
+ )
+ )
+
+
+def _generate_entry_point_rule(name: str, script: str, pkg: str) -> str:
+ """Generate a Bazel `py_binary` rule for an entry point script.
+
+ Note that the script is used to determine the name of the target. The name of
+ entry point targets should be uniuqe to avoid conflicts with existing sources or
+ directories within a wheel.
+
+ Args:
+ name (str): The name of the generated py_binary.
+ script (str): The path to the entry point's python file.
+ pkg (str): The package owning the entry point. This is expected to
+ match up with the `py_library` defined for each repository.
+
+
+ Returns:
+ str: A `py_binary` instantiation.
+ """
+ return textwrap.dedent(
+ """\
+ py_binary(
+ name = "{name}",
+ srcs = ["{src}"],
+ # This makes this directory a top-level in the python import
+ # search path for anything that depends on this.
+ imports = ["."],
+ deps = ["{pkg}"],
+ )
+ """.format(
+ name=name, src=str(script).replace("\\", "/"), pkg=pkg
+ )
+ )
+
+
+def _generate_copy_commands(src, dest, is_executable=False) -> str:
+ """Generate a [@bazel_skylib//rules:copy_file.bzl%copy_file][cf] target
+
+ [cf]: https://github.com/bazelbuild/bazel-skylib/blob/1.1.1/docs/copy_file_doc.md
+
+ Args:
+ src (str): The label for the `src` attribute of [copy_file][cf]
+ dest (str): The label for the `out` attribute of [copy_file][cf]
+ is_executable (bool, optional): Whether or not the file being copied is executable.
+ sets `is_executable` for [copy_file][cf]
+
+ Returns:
+ str: A `copy_file` instantiation.
+ """
+ return textwrap.dedent(
+ """\
+ copy_file(
+ name = "{dest}.copy",
+ src = "{src}",
+ out = "{dest}",
+ is_executable = {is_executable},
+ )
+ """.format(
+ src=src,
+ dest=dest,
+ is_executable=is_executable,
+ )
+ )
+
+
+def _generate_build_file_contents(
+ name: str,
+ dependencies: List[str],
+ whl_file_deps: List[str],
+ data_exclude: List[str],
+ tags: List[str],
+ srcs_exclude: List[str] = [],
+ data: List[str] = [],
+ additional_content: List[str] = [],
+) -> str:
+ """Generate a BUILD file for an unzipped Wheel
+
+ Args:
+ name: the target name of the py_library
+ dependencies: a list of Bazel labels pointing to dependencies of the library
+ whl_file_deps: a list of Bazel labels pointing to wheel file dependencies of this wheel.
+ data_exclude: more patterns to exclude from the data attribute of generated py_library rules.
+ tags: list of tags to apply to generated py_library rules.
+ additional_content: A list of additional content to append to the BUILD file.
+
+ Returns:
+ A complete BUILD file as a string
+
+ We allow for empty Python sources as for Wheels containing only compiled C code
+ there may be no Python sources whatsoever (e.g. packages written in Cython: like `pymssql`).
+ """
+
+ data_exclude = list(
+ set(
+ [
+ "**/* *",
+ "**/*.py",
+ "**/*.pyc",
+ "**/*.pyc.*", # During pyc creation, temp files named *.pyc.NNNN are created
+ # RECORD is known to contain sha256 checksums of files which might include the checksums
+ # of generated files produced when wheels are installed. The file is ignored to avoid
+ # Bazel caching issues.
+ "**/*.dist-info/RECORD",
+ ]
+ + data_exclude
+ )
+ )
+
+ return "\n".join(
+ [
+ textwrap.dedent(
+ """\
+ load("@rules_python//python:defs.bzl", "py_library", "py_binary")
+ load("@bazel_skylib//rules:copy_file.bzl", "copy_file")
+
+ package(default_visibility = ["//visibility:public"])
+
+ filegroup(
+ name = "{dist_info_label}",
+ srcs = glob(["site-packages/*.dist-info/**"], allow_empty = True),
+ )
+
+ filegroup(
+ name = "{data_label}",
+ srcs = glob(["data/**"], allow_empty = True),
+ )
+
+ filegroup(
+ name = "{whl_file_label}",
+ srcs = glob(["*.whl"], allow_empty = True),
+ data = [{whl_file_deps}],
+ )
+
+ py_library(
+ name = "{name}",
+ srcs = glob(["site-packages/**/*.py"], exclude={srcs_exclude}, allow_empty = True),
+ data = {data} + glob(["site-packages/**/*"], exclude={data_exclude}),
+ # This makes this directory a top-level in the python import
+ # search path for anything that depends on this.
+ imports = ["site-packages"],
+ deps = [{dependencies}],
+ tags = [{tags}],
+ )
+ """.format(
+ name=name,
+ dependencies=",".join(sorted(dependencies)),
+ data_exclude=json.dumps(sorted(data_exclude)),
+ whl_file_label=bazel.WHEEL_FILE_LABEL,
+ whl_file_deps=",".join(sorted(whl_file_deps)),
+ tags=",".join(sorted(['"%s"' % t for t in tags])),
+ data_label=bazel.DATA_LABEL,
+ dist_info_label=bazel.DIST_INFO_LABEL,
+ entry_point_prefix=bazel.WHEEL_ENTRY_POINT_PREFIX,
+ srcs_exclude=json.dumps(sorted(srcs_exclude)),
+ data=json.dumps(sorted(data)),
+ )
+ )
+ ]
+ + additional_content
+ )
+
+
+def _extract_wheel(
+ wheel_file: str,
+ extras: Dict[str, Set[str]],
+ pip_data_exclude: List[str],
+ enable_implicit_namespace_pkgs: bool,
+ repo_prefix: str,
+ installation_dir: Path = Path("."),
+ annotation: Optional[annotation.Annotation] = None,
+) -> None:
+ """Extracts wheel into given directory and creates py_library and filegroup targets.
+
+ Args:
+ wheel_file: the filepath of the .whl
+ installation_dir: the destination directory for installation of the wheel.
+ extras: a list of extras to add as dependencies for the installed wheel
+ pip_data_exclude: list of file patterns to exclude from the generated data section of the py_library
+ enable_implicit_namespace_pkgs: if true, disables conversion of implicit namespace packages and will unzip as-is
+ annotation: An optional set of annotations to apply to the BUILD contents of the wheel.
+ """
+
+ whl = wheel.Wheel(wheel_file)
+ whl.unzip(installation_dir)
+
+ if not enable_implicit_namespace_pkgs:
+ _setup_namespace_pkg_compatibility(installation_dir)
+
+ extras_requested = extras[whl.name] if whl.name in extras else set()
+ # Packages may create dependency cycles when specifying optional-dependencies / 'extras'.
+ # Example: github.com/google/etils/blob/a0b71032095db14acf6b33516bca6d885fe09e35/pyproject.toml#L32.
+ self_edge_dep = set([whl.name])
+ whl_deps = sorted(whl.dependencies(extras_requested) - self_edge_dep)
+
+ sanitised_dependencies = [
+ bazel.sanitised_repo_library_label(d, repo_prefix=repo_prefix) for d in whl_deps
+ ]
+ sanitised_wheel_file_dependencies = [
+ bazel.sanitised_repo_file_label(d, repo_prefix=repo_prefix) for d in whl_deps
+ ]
+
+ entry_points = []
+ for name, (module, attribute) in sorted(whl.entry_points().items()):
+ # There is an extreme edge-case with entry_points that end with `.py`
+ # See: https://github.com/bazelbuild/bazel/blob/09c621e4cf5b968f4c6cdf905ab142d5961f9ddc/src/test/java/com/google/devtools/build/lib/rules/python/PyBinaryConfiguredTargetTest.java#L174
+ entry_point_without_py = f"{name[:-3]}_py" if name.endswith(".py") else name
+ entry_point_target_name = (
+ f"{bazel.WHEEL_ENTRY_POINT_PREFIX}_{entry_point_without_py}"
+ )
+ entry_point_script_name = f"{entry_point_target_name}.py"
+ (installation_dir / entry_point_script_name).write_text(
+ _generate_entry_point_contents(module, attribute)
+ )
+ entry_points.append(
+ _generate_entry_point_rule(
+ entry_point_target_name,
+ entry_point_script_name,
+ bazel.PY_LIBRARY_LABEL,
+ )
+ )
+
+ with open(os.path.join(installation_dir, "BUILD.bazel"), "w") as build_file:
+ additional_content = entry_points
+ data = []
+ data_exclude = pip_data_exclude
+ srcs_exclude = []
+ if annotation:
+ for src, dest in annotation.copy_files.items():
+ data.append(dest)
+ additional_content.append(_generate_copy_commands(src, dest))
+ for src, dest in annotation.copy_executables.items():
+ data.append(dest)
+ additional_content.append(
+ _generate_copy_commands(src, dest, is_executable=True)
+ )
+ data.extend(annotation.data)
+ data_exclude.extend(annotation.data_exclude_glob)
+ srcs_exclude.extend(annotation.srcs_exclude_glob)
+ if annotation.additive_build_content:
+ additional_content.append(annotation.additive_build_content)
+
+ contents = _generate_build_file_contents(
+ name=bazel.PY_LIBRARY_LABEL,
+ dependencies=sanitised_dependencies,
+ whl_file_deps=sanitised_wheel_file_dependencies,
+ data_exclude=data_exclude,
+ data=data,
+ srcs_exclude=srcs_exclude,
+ tags=["pypi_name=" + whl.name, "pypi_version=" + whl.version],
+ additional_content=additional_content,
+ )
+ build_file.write(contents)
+
+
+def main() -> None:
+ parser = argparse.ArgumentParser(
+ description="Build and/or fetch a single wheel based on the requirement passed in"
+ )
+ parser.add_argument(
+ "--requirement",
+ action="store",
+ required=True,
+ help="A single PEP508 requirement specifier string.",
+ )
+ parser.add_argument(
+ "--annotation",
+ type=annotation.annotation_from_str_path,
+ help="A json encoded file containing annotations for rendered packages.",
+ )
+ arguments.parse_common_args(parser)
+ args = parser.parse_args()
+ deserialized_args = dict(vars(args))
+ arguments.deserialize_structured_args(deserialized_args)
+
+ _configure_reproducible_wheels()
+
+ pip_args = (
+ [sys.executable, "-m", "pip"]
+ + (["--isolated"] if args.isolated else [])
+ + (["download", "--only-binary=:all:"] if args.download_only else ["wheel"])
+ + ["--no-deps"]
+ + deserialized_args["extra_pip_args"]
+ )
+
+ requirement_file = NamedTemporaryFile(mode="wb", delete=False)
+ try:
+ requirement_file.write(args.requirement.encode("utf-8"))
+ requirement_file.flush()
+ # Close the file so pip is allowed to read it when running on Windows.
+ # For more information, see: https://bugs.python.org/issue14243
+ requirement_file.close()
+ # Requirement specific args like --hash can only be passed in a requirements file,
+ # so write our single requirement into a temp file in case it has any of those flags.
+ pip_args.extend(["-r", requirement_file.name])
+
+ env = os.environ.copy()
+ env.update(deserialized_args["environment"])
+ # Assumes any errors are logged by pip so do nothing. This command will fail if pip fails
+ subprocess.run(pip_args, check=True, env=env)
+ finally:
+ try:
+ os.unlink(requirement_file.name)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
+
+ name, extras_for_pkg = _parse_requirement_for_extra(args.requirement)
+ extras = {name: extras_for_pkg} if extras_for_pkg and name else dict()
+
+ whl = next(iter(glob.glob("*.whl")))
+ _extract_wheel(
+ wheel_file=whl,
+ extras=extras,
+ pip_data_exclude=deserialized_args["pip_data_exclude"],
+ enable_implicit_namespace_pkgs=args.enable_implicit_namespace_pkgs,
+ repo_prefix=args.repo_prefix,
+ annotation=args.annotation,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/pip_install/tools/wheel_installer/wheel_installer_test.py b/python/pip_install/tools/wheel_installer/wheel_installer_test.py
new file mode 100644
index 0000000..8758b67
--- /dev/null
+++ b/python/pip_install/tools/wheel_installer/wheel_installer_test.py
@@ -0,0 +1,108 @@
+# 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.
+
+import os
+import shutil
+import tempfile
+import unittest
+from pathlib import Path
+
+from python.pip_install.tools.wheel_installer import wheel_installer
+
+
+class TestRequirementExtrasParsing(unittest.TestCase):
+ def test_parses_requirement_for_extra(self) -> None:
+ cases = [
+ ("name[foo]", ("name", frozenset(["foo"]))),
+ ("name[ Foo123 ]", ("name", frozenset(["Foo123"]))),
+ (" name1[ foo ] ", ("name1", frozenset(["foo"]))),
+ ("Name[foo]", ("name", frozenset(["foo"]))),
+ ("name_foo[bar]", ("name-foo", frozenset(["bar"]))),
+ (
+ "name [fred,bar] @ http://foo.com ; python_version=='2.7'",
+ ("name", frozenset(["fred", "bar"])),
+ ),
+ (
+ "name[quux, strange];python_version<'2.7' and platform_version=='2'",
+ ("name", frozenset(["quux", "strange"])),
+ ),
+ (
+ "name; (os_name=='a' or os_name=='b') and os_name=='c'",
+ (None, None),
+ ),
+ (
+ "name@http://foo.com",
+ (None, None),
+ ),
+ ]
+
+ for case, expected in cases:
+ with self.subTest():
+ self.assertTupleEqual(
+ wheel_installer._parse_requirement_for_extra(case), expected
+ )
+
+
+class BazelTestCase(unittest.TestCase):
+ def test_generate_entry_point_contents(self):
+ got = wheel_installer._generate_entry_point_contents("sphinx.cmd.build", "main")
+ want = """#!/usr/bin/env python3
+import sys
+from sphinx.cmd.build import main
+if __name__ == "__main__":
+ sys.exit(main())
+"""
+ self.assertEqual(got, want)
+
+ def test_generate_entry_point_contents_with_shebang(self):
+ got = wheel_installer._generate_entry_point_contents(
+ "sphinx.cmd.build", "main", shebang="#!/usr/bin/python"
+ )
+ want = """#!/usr/bin/python
+import sys
+from sphinx.cmd.build import main
+if __name__ == "__main__":
+ sys.exit(main())
+"""
+ self.assertEqual(got, want)
+
+
+class TestWhlFilegroup(unittest.TestCase):
+ def setUp(self) -> None:
+ self.wheel_name = "example_minimal_package-0.0.1-py3-none-any.whl"
+ self.wheel_dir = tempfile.mkdtemp()
+ self.wheel_path = os.path.join(self.wheel_dir, self.wheel_name)
+ shutil.copy(os.path.join("examples", "wheel", self.wheel_name), self.wheel_dir)
+
+ def tearDown(self):
+ shutil.rmtree(self.wheel_dir)
+
+ def test_wheel_exists(self) -> None:
+ wheel_installer._extract_wheel(
+ self.wheel_path,
+ installation_dir=Path(self.wheel_dir),
+ extras={},
+ pip_data_exclude=[],
+ enable_implicit_namespace_pkgs=False,
+ repo_prefix="prefix_",
+ )
+
+ self.assertIn(self.wheel_name, os.listdir(self.wheel_dir))
+ with open("{}/BUILD.bazel".format(self.wheel_dir)) as build_file:
+ build_file_content = build_file.read()
+ self.assertIn("filegroup", build_file_content)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel
new file mode 100644
index 0000000..10af17e
--- /dev/null
+++ b/python/private/BUILD.bazel
@@ -0,0 +1,106 @@
+# Copyright 2021 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//:bzl_library.bzl", "bzl_library")
+load("//python:versions.bzl", "print_toolchains_checksums")
+load(":stamp.bzl", "stamp_build_setting")
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]) + ["//python/private/proto:distribution"],
+ visibility = ["//python:__pkg__"],
+)
+
+# Filegroup of bzl files that can be used by downstream rules for documentation generation
+filegroup(
+ name = "bzl",
+ srcs = glob(["**/*.bzl"]),
+ visibility = ["//python:__pkg__"],
+)
+
+bzl_library(
+ name = "reexports_bzl",
+ srcs = ["reexports.bzl"],
+ visibility = [
+ "//docs:__pkg__",
+ "//python:__pkg__",
+ ],
+ deps = [":bazel_tools_bzl"],
+)
+
+bzl_library(
+ name = "util_bzl",
+ srcs = ["util.bzl"],
+ visibility = [
+ "//docs:__subpackages__",
+ "//python:__subpackages__",
+ ],
+ deps = ["@bazel_skylib//lib:types"],
+)
+
+bzl_library(
+ name = "py_cc_toolchain_bzl",
+ srcs = [
+ "py_cc_toolchain_macro.bzl",
+ "py_cc_toolchain_rule.bzl",
+ ],
+ visibility = [
+ "//docs:__subpackages__",
+ "//python/cc:__pkg__",
+ ],
+ deps = [
+ ":py_cc_toolchain_info_bzl",
+ ":util_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_cc_toolchain_info_bzl",
+ srcs = ["py_cc_toolchain_info.bzl"],
+ visibility = ["//python/cc:__pkg__"],
+)
+
+# @bazel_tools can't define bzl_library itself, so we just put a wrapper around it.
+bzl_library(
+ name = "bazel_tools_bzl",
+ srcs = [
+ "@bazel_tools//tools/python:srcs_version.bzl",
+ "@bazel_tools//tools/python:toolchain.bzl",
+ "@bazel_tools//tools/python:utils.bzl",
+ ],
+ visibility = ["//python:__pkg__"],
+)
+
+# Needed to define bzl_library targets for docgen. (We don't define the
+# bzl_library target here because it'd give our users a transitive dependency
+# on Skylib.)
+exports_files(
+ [
+ "coverage.patch",
+ "py_package.bzl",
+ "py_wheel.bzl",
+ "reexports.bzl",
+ "stamp.bzl",
+ "util.bzl",
+ "py_cc_toolchain_rule.bzl",
+ ],
+ visibility = ["//docs:__pkg__"],
+)
+
+# Used to determine the use of `--stamp` in Starlark rules
+stamp_build_setting(name = "stamp")
+
+print_toolchains_checksums(name = "print_toolchains_checksums")
diff --git a/python/private/bzlmod_enabled.bzl b/python/private/bzlmod_enabled.bzl
new file mode 100644
index 0000000..8483998
--- /dev/null
+++ b/python/private/bzlmod_enabled.bzl
@@ -0,0 +1,18 @@
+#
+# 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.
+
+"""Variable to check if bzlmod is enabled"""
+
+# When bzlmod is enabled, canonical repos names have @@ in them, while under
+# workspace builds, there is never a @@ in labels.
+BZLMOD_ENABLED = "@@" in str(Label("//:unused"))
diff --git a/python/private/coverage.patch b/python/private/coverage.patch
new file mode 100644
index 0000000..cb4402e
--- /dev/null
+++ b/python/private/coverage.patch
@@ -0,0 +1,17 @@
+# Because of how coverage is run, the current directory is the first in
+# sys.path. This is a problem for the tests, because they may import a module of
+# the same name as a module in the current directory.
+#
+# NOTE @aignas 2023-06-05: we have to do this before anything from coverage gets
+# imported.
+diff --git a/coverage/__main__.py b/coverage/__main__.py
+index 79aa4e2b..291fcff8 100644
+--- a/coverage/__main__.py
++++ b/coverage/__main__.py
+@@ -4,5 +4,6 @@
+ """Coverage.py's main entry point."""
+
+ import sys
++sys.path.append(sys.path.pop(0))
+ from coverage.cmdline import main
+ sys.exit(main())
diff --git a/python/private/coverage_deps.bzl b/python/private/coverage_deps.bzl
new file mode 100644
index 0000000..93938e9
--- /dev/null
+++ b/python/private/coverage_deps.bzl
@@ -0,0 +1,147 @@
+# 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.
+
+"""Dependencies for coverage.py used by the hermetic toolchain.
+"""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("//python/private:version_label.bzl", "version_label")
+
+# Update with './tools/update_coverage_deps.py <version>'
+#START: managed by update_coverage_deps.py script
+_coverage_deps = {
+ "cp310": {
+ "aarch64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/3d/80/7060a445e1d2c9744b683dc935248613355657809d6c6b2716cdf4ca4766/coverage-7.2.7-cp310-cp310-macosx_11_0_arm64.whl",
+ "6d040ef7c9859bb11dfeb056ff5b3872436e3b5e401817d87a31e1750b9ae2fb",
+ ),
+ "aarch64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/b8/9d/926fce7e03dbfc653104c2d981c0fa71f0572a9ebd344d24c573bd6f7c4f/coverage-7.2.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
+ "ba90a9563ba44a72fda2e85302c3abc71c5589cea608ca16c22b9804262aaeb6",
+ ),
+ "x86_64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/01/24/be01e62a7bce89bcffe04729c540382caa5a06bee45ae42136c93e2499f5/coverage-7.2.7-cp310-cp310-macosx_10_9_x86_64.whl",
+ "d39b5b4f2a66ccae8b7263ac3c8170994b65266797fb96cbbfd3fb5b23921db8",
+ ),
+ "x86_64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/b4/bd/1b2331e3a04f4cc9b7b332b1dd0f3a1261dfc4114f8479bebfcc2afee9e8/coverage-7.2.7-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
+ "31563e97dae5598556600466ad9beea39fb04e0229e61c12eaa206e0aa202063",
+ ),
+ },
+ "cp311": {
+ "aarch64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/67/d7/cd8fe689b5743fffac516597a1222834c42b80686b99f5b44ef43ccc2a43/coverage-7.2.7-cp311-cp311-macosx_11_0_arm64.whl",
+ "5baa06420f837184130752b7c5ea0808762083bf3487b5038d68b012e5937dbe",
+ ),
+ "aarch64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/8c/95/16eed713202406ca0a37f8ac259bbf144c9d24f9b8097a8e6ead61da2dbb/coverage-7.2.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
+ "fdec9e8cbf13a5bf63290fc6013d216a4c7232efb51548594ca3631a7f13c3a3",
+ ),
+ "x86_64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/c6/fa/529f55c9a1029c840bcc9109d5a15ff00478b7ff550a1ae361f8745f8ad5/coverage-7.2.7-cp311-cp311-macosx_10_9_x86_64.whl",
+ "06a9a2be0b5b576c3f18f1a241f0473575c4a26021b52b2a85263a00f034d51f",
+ ),
+ "x86_64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/a7/cd/3ce94ad9d407a052dc2a74fbeb1c7947f442155b28264eb467ee78dea812/coverage-7.2.7-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
+ "63426706118b7f5cf6bb6c895dc215d8a418d5952544042c8a2d9fe87fcf09cb",
+ ),
+ },
+ "cp38": {
+ "aarch64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/28/d7/9a8de57d87f4bbc6f9a6a5ded1eaac88a89bf71369bb935dac3c0cf2893e/coverage-7.2.7-cp38-cp38-macosx_11_0_arm64.whl",
+ "3d376df58cc111dc8e21e3b6e24606b5bb5dee6024f46a5abca99124b2229ef5",
+ ),
+ "aarch64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/c8/e4/e6182e4697665fb594a7f4e4f27cb3a4dd00c2e3d35c5c706765de8c7866/coverage-7.2.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
+ "5e330fc79bd7207e46c7d7fd2bb4af2963f5f635703925543a70b99574b0fea9",
+ ),
+ "x86_64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/c6/fc/be19131010930a6cf271da48202c8cc1d3f971f68c02fb2d3a78247f43dc/coverage-7.2.7-cp38-cp38-macosx_10_9_x86_64.whl",
+ "54b896376ab563bd38453cecb813c295cf347cf5906e8b41d340b0321a5433e5",
+ ),
+ "x86_64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/44/55/49f65ccdd4dfd6d5528e966b28c37caec64170c725af32ab312889d2f857/coverage-7.2.7-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
+ "8d13c64ee2d33eccf7437961b6ea7ad8673e2be040b4f7fd4fd4d4d28d9ccb1e",
+ ),
+ },
+ "cp39": {
+ "aarch64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/ca/0c/3dfeeb1006c44b911ee0ed915350db30325d01808525ae7cc8d57643a2ce/coverage-7.2.7-cp39-cp39-macosx_11_0_arm64.whl",
+ "06fb182e69f33f6cd1d39a6c597294cff3143554b64b9825d1dc69d18cc2fff2",
+ ),
+ "aarch64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/61/af/5964b8d7d9a5c767785644d9a5a63cacba9a9c45cc42ba06d25895ec87be/coverage-7.2.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl",
+ "201e7389591af40950a6480bd9edfa8ed04346ff80002cec1a66cac4549c1ad7",
+ ),
+ "x86_64-apple-darwin": (
+ "https://files.pythonhosted.org/packages/88/da/495944ebf0ad246235a6bd523810d9f81981f9b81c6059ba1f56e943abe0/coverage-7.2.7-cp39-cp39-macosx_10_9_x86_64.whl",
+ "537891ae8ce59ef63d0123f7ac9e2ae0fc8b72c7ccbe5296fec45fd68967b6c9",
+ ),
+ "x86_64-unknown-linux-gnu": (
+ "https://files.pythonhosted.org/packages/fe/57/e4f8ad64d84ca9e759d783a052795f62a9f9111585e46068845b1cb52c2b/coverage-7.2.7-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl",
+ "6f48351d66575f535669306aa7d6d6f71bc43372473b54a832222803eb956fd1",
+ ),
+ },
+}
+#END: managed by update_coverage_deps.py script
+
+_coverage_patch = Label("//python/private:coverage.patch")
+
+def coverage_dep(name, python_version, platform, visibility):
+ """Register a singe coverage dependency based on the python version and platform.
+
+ Args:
+ name: The name of the registered repository.
+ python_version: The full python version.
+ platform: The platform, which can be found in //python:versions.bzl PLATFORMS dict.
+ visibility: The visibility of the coverage tool.
+
+ Returns:
+ The label of the coverage tool if the platform is supported, otherwise - None.
+ """
+ if "windows" in platform:
+ # NOTE @aignas 2023-01-19: currently we do not support windows as the
+ # upstream coverage wrapper is written in shell. Do not log any warning
+ # for now as it is not actionable.
+ return None
+
+ abi = "cp" + version_label(python_version)
+ url, sha256 = _coverage_deps.get(abi, {}).get(platform, (None, ""))
+
+ if url == None:
+ # Some wheels are not present for some builds, so let's silently ignore those.
+ return None
+
+ maybe(
+ http_archive,
+ name = name,
+ build_file_content = """
+filegroup(
+ name = "coverage",
+ srcs = ["coverage/__main__.py"],
+ data = glob(["coverage/*.py", "coverage/**/*.py", "coverage/*.so"]),
+ visibility = {visibility},
+)
+ """.format(
+ visibility = visibility,
+ ),
+ patch_args = ["-p1"],
+ patches = [_coverage_patch],
+ sha256 = sha256,
+ type = "zip",
+ urls = [url],
+ )
+
+ return "@{name}//:coverage".format(name = name)
diff --git a/python/private/current_py_cc_headers.bzl b/python/private/current_py_cc_headers.bzl
new file mode 100644
index 0000000..be7f8f8
--- /dev/null
+++ b/python/private/current_py_cc_headers.bzl
@@ -0,0 +1,41 @@
+# 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.
+
+"""Implementation of current_py_cc_headers rule."""
+
+def _current_py_cc_headers_impl(ctx):
+ py_cc_toolchain = ctx.toolchains["//python/cc:toolchain_type"].py_cc_toolchain
+ return py_cc_toolchain.headers.providers_map.values()
+
+current_py_cc_headers = rule(
+ implementation = _current_py_cc_headers_impl,
+ toolchains = ["//python/cc:toolchain_type"],
+ provides = [CcInfo],
+ doc = """\
+Provides the currently active Python toolchain's C headers.
+
+This is a wrapper around the underlying `cc_library()` for the
+C headers for the consuming target's currently active Python toolchain.
+
+To use, simply depend on this target where you would have wanted the
+toolchain's underlying `:python_headers` target:
+
+```starlark
+cc_library(
+ name = "foo",
+ deps = ["@rules_python//python/cc:current_py_cc_headers"]
+)
+```
+""",
+)
diff --git a/python/private/normalize_name.bzl b/python/private/normalize_name.bzl
new file mode 100644
index 0000000..aaeca80
--- /dev/null
+++ b/python/private/normalize_name.bzl
@@ -0,0 +1,61 @@
+# 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.
+
+"""
+Normalize a PyPI package name to allow consistent label names
+
+Note we chose `_` instead of `-` as a separator as there are certain
+requirements around Bazel labels that we need to consider.
+
+From the Bazel docs:
+> Package names must be composed entirely of characters drawn from the set
+> A-Z, a–z, 0–9, '/', '-', '.', and '_', and cannot start with a slash.
+
+However, due to restrictions on Bazel labels we also cannot allow hyphens.
+See https://github.com/bazelbuild/bazel/issues/6841
+
+Further, rules_python automatically adds the repository root to the
+PYTHONPATH, meaning a package that has the same name as a module is picked
+up. We workaround this by prefixing with `<hub_name>_`.
+
+Alternatively we could require
+`--noexperimental_python_import_all_repositories` be set, however this
+breaks rules_docker.
+See: https://github.com/bazelbuild/bazel/issues/2636
+
+Also see Python spec on normalizing package names:
+https://packaging.python.org/en/latest/specifications/name-normalization/
+"""
+
+# Keep in sync with ../pip_install/tools/lib/bazel.py
+def normalize_name(name):
+ """normalize a PyPI package name and return a valid bazel label.
+
+ Args:
+ name: str, the PyPI package name.
+
+ Returns:
+ a normalized name as a string.
+ """
+ name = name.replace("-", "_").replace(".", "_").lower()
+ if "__" not in name:
+ return name
+
+ # Handle the edge-case where there are consecutive `-`, `_` or `.` characters,
+ # which is a valid Python package name.
+ return "_".join([
+ part
+ for part in name.split("_")
+ if part
+ ])
diff --git a/python/private/proto/BUILD.bazel b/python/private/proto/BUILD.bazel
new file mode 100644
index 0000000..65c0944
--- /dev/null
+++ b/python/private/proto/BUILD.bazel
@@ -0,0 +1,46 @@
+# Copyright 2022 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//:bzl_library.bzl", "bzl_library")
+load("@rules_proto//proto:defs.bzl", "proto_lang_toolchain")
+
+package(default_visibility = ["//visibility:private"])
+
+licenses(["notice"])
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//python/private:__pkg__"],
+)
+
+bzl_library(
+ name = "py_proto_library_bzl",
+ srcs = ["py_proto_library.bzl"],
+ visibility = ["//python:__pkg__"],
+ deps = [
+ "//python:defs_bzl",
+ "@rules_proto//proto:defs",
+ ],
+)
+
+proto_lang_toolchain(
+ name = "python_toolchain",
+ command_line = "--python_out=%s",
+ progress_message = "Generating Python proto_library %{label}",
+ runtime = "@com_google_protobuf//:protobuf_python",
+ # NOTE: This isn't *actually* public. It's an implicit dependency of py_proto_library,
+ # so must be public so user usages of the rule can reference it.
+ visibility = ["//visibility:public"],
+)
diff --git a/python/private/proto/py_proto_library.bzl b/python/private/proto/py_proto_library.bzl
new file mode 100644
index 0000000..9377c85
--- /dev/null
+++ b/python/private/proto/py_proto_library.bzl
@@ -0,0 +1,208 @@
+# Copyright 2022 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 implementation of the `py_proto_library` rule and its aspect."""
+
+load("@rules_proto//proto:defs.bzl", "ProtoInfo", "proto_common")
+load("//python:defs.bzl", "PyInfo")
+
+ProtoLangToolchainInfo = proto_common.ProtoLangToolchainInfo
+
+_PyProtoInfo = provider(
+ doc = "Encapsulates information needed by the Python proto rules.",
+ fields = {
+ "imports": """
+ (depset[str]) The field forwarding PyInfo.imports coming from
+ the proto language runtime dependency.""",
+ "runfiles_from_proto_deps": """
+ (depset[File]) Files from the transitive closure implicit proto
+ dependencies""",
+ "transitive_sources": """(depset[File]) The Python sources.""",
+ },
+)
+
+def _filter_provider(provider, *attrs):
+ return [dep[provider] for attr in attrs for dep in attr if provider in dep]
+
+def _py_proto_aspect_impl(target, ctx):
+ """Generates and compiles Python code for a proto_library.
+
+ The function runs protobuf compiler on the `proto_library` target generating
+ a .py file for each .proto file.
+
+ Args:
+ target: (Target) A target providing `ProtoInfo`. Usually this means a
+ `proto_library` target, but not always; you must expect to visit
+ non-`proto_library` targets, too.
+ ctx: (RuleContext) The rule context.
+
+ Returns:
+ ([_PyProtoInfo]) Providers collecting transitive information about
+ generated files.
+ """
+
+ _proto_library = ctx.rule.attr
+
+ # Check Proto file names
+ for proto in target[ProtoInfo].direct_sources:
+ if proto.is_source and "-" in proto.dirname:
+ fail("Cannot generate Python code for a .proto whose path contains '-' ({}).".format(
+ proto.path,
+ ))
+
+ proto_lang_toolchain_info = ctx.attr._aspect_proto_toolchain[ProtoLangToolchainInfo]
+ api_deps = [proto_lang_toolchain_info.runtime]
+
+ generated_sources = []
+ proto_info = target[ProtoInfo]
+ if proto_info.direct_sources:
+ # Generate py files
+ generated_sources = proto_common.declare_generated_files(
+ actions = ctx.actions,
+ proto_info = proto_info,
+ extension = "_pb2.py",
+ name_mapper = lambda name: name.replace("-", "_").replace(".", "/"),
+ )
+
+ # Handles multiple repository and virtual import cases
+ proto_root = proto_info.proto_source_root
+ if proto_root.startswith(ctx.bin_dir.path):
+ plugin_output = proto_root
+ else:
+ plugin_output = ctx.bin_dir.path + "/" + proto_root
+
+ if plugin_output == ".":
+ plugin_output = ctx.bin_dir.path
+
+ proto_common.compile(
+ actions = ctx.actions,
+ proto_info = proto_info,
+ proto_lang_toolchain_info = proto_lang_toolchain_info,
+ generated_files = generated_sources,
+ plugin_output = plugin_output,
+ )
+
+ # Generated sources == Python sources
+ python_sources = generated_sources
+
+ deps = _filter_provider(_PyProtoInfo, getattr(_proto_library, "deps", []))
+ runfiles_from_proto_deps = depset(
+ transitive = [dep[DefaultInfo].default_runfiles.files for dep in api_deps] +
+ [dep.runfiles_from_proto_deps for dep in deps],
+ )
+ transitive_sources = depset(
+ direct = python_sources,
+ transitive = [dep.transitive_sources for dep in deps],
+ )
+
+ return [
+ _PyProtoInfo(
+ imports = depset(
+ transitive = [dep[PyInfo].imports for dep in api_deps],
+ ),
+ runfiles_from_proto_deps = runfiles_from_proto_deps,
+ transitive_sources = transitive_sources,
+ ),
+ ]
+
+_py_proto_aspect = aspect(
+ implementation = _py_proto_aspect_impl,
+ attrs = {
+ "_aspect_proto_toolchain": attr.label(
+ default = ":python_toolchain",
+ ),
+ },
+ attr_aspects = ["deps"],
+ required_providers = [ProtoInfo],
+ provides = [_PyProtoInfo],
+)
+
+def _py_proto_library_rule(ctx):
+ """Merges results of `py_proto_aspect` in `deps`.
+
+ Args:
+ ctx: (RuleContext) The rule context.
+ Returns:
+ ([PyInfo, DefaultInfo, OutputGroupInfo])
+ """
+ if not ctx.attr.deps:
+ fail("'deps' attribute mustn't be empty.")
+
+ pyproto_infos = _filter_provider(_PyProtoInfo, ctx.attr.deps)
+ default_outputs = depset(
+ transitive = [info.transitive_sources for info in pyproto_infos],
+ )
+
+ return [
+ DefaultInfo(
+ files = default_outputs,
+ default_runfiles = ctx.runfiles(transitive_files = depset(
+ transitive =
+ [default_outputs] +
+ [info.runfiles_from_proto_deps for info in pyproto_infos],
+ )),
+ ),
+ OutputGroupInfo(
+ default = depset(),
+ ),
+ PyInfo(
+ transitive_sources = default_outputs,
+ imports = depset(transitive = [info.imports for info in pyproto_infos]),
+ # Proto always produces 2- and 3- compatible source files
+ has_py2_only_sources = False,
+ has_py3_only_sources = False,
+ ),
+ ]
+
+py_proto_library = rule(
+ implementation = _py_proto_library_rule,
+ doc = """
+ Use `py_proto_library` to generate Python libraries from `.proto` files.
+
+ The convention is to name the `py_proto_library` rule `foo_py_pb2`,
+ when it is wrapping `proto_library` rule `foo_proto`.
+
+ `deps` must point to a `proto_library` rule.
+
+ Example:
+
+```starlark
+py_library(
+ name = "lib",
+ deps = [":foo_py_pb2"],
+)
+
+py_proto_library(
+ name = "foo_py_pb2",
+ deps = [":foo_proto"],
+)
+
+proto_library(
+ name = "foo_proto",
+ srcs = ["foo.proto"],
+)
+```""",
+ attrs = {
+ "deps": attr.label_list(
+ doc = """
+ The list of `proto_library` rules to generate Python libraries for.
+
+ Usually this is just the one target: the proto library of interest.
+ It can be any target providing `ProtoInfo`.""",
+ providers = [ProtoInfo],
+ aspects = [_py_proto_aspect],
+ ),
+ },
+ provides = [PyInfo],
+)
diff --git a/python/private/py_cc_toolchain_info.bzl b/python/private/py_cc_toolchain_info.bzl
new file mode 100644
index 0000000..e7afc10
--- /dev/null
+++ b/python/private/py_cc_toolchain_info.bzl
@@ -0,0 +1,43 @@
+# 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.
+
+"""Implementation of PyCcToolchainInfo."""
+
+PyCcToolchainInfo = provider(
+ doc = "C/C++ information about the Python runtime.",
+ fields = {
+ "headers": """\
+(struct) Information about the header files, with fields:
+ * providers_map: a dict of string to provider instances. The key should be
+ a fully qualified name (e.g. `@rules_foo//bar:baz.bzl#MyInfo`) of the
+ provider to uniquely identify its type.
+
+ The following keys are always present:
+ * CcInfo: the CcInfo provider instance for the headers.
+ * DefaultInfo: the DefaultInfo provider instance for the headers.
+
+ A map is used to allow additional providers from the originating headers
+ target (typically a `cc_library`) to be propagated to consumers (directly
+ exposing a Target object can cause memory issues and is an anti-pattern).
+
+ When consuming this map, it's suggested to use `providers_map.values()` to
+ return all providers; or copy the map and filter out or replace keys as
+ appropriate. Note that any keys begining with `_` (underscore) are
+ considered private and should be forward along as-is (this better allows
+ e.g. `:current_py_cc_headers` to act as the underlying headers target it
+ represents).
+""",
+ "python_version": "(str) The Python Major.Minor version.",
+ },
+)
diff --git a/python/private/py_cc_toolchain_macro.bzl b/python/private/py_cc_toolchain_macro.bzl
new file mode 100644
index 0000000..35276f7
--- /dev/null
+++ b/python/private/py_cc_toolchain_macro.bzl
@@ -0,0 +1,31 @@
+# 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.
+
+"""Fronting macro for the py_cc_toolchain rule."""
+
+load(":py_cc_toolchain_rule.bzl", _py_cc_toolchain = "py_cc_toolchain")
+load(":util.bzl", "add_tag")
+
+# A fronting macro is used because macros have user-observable behavior;
+# using one from the onset avoids introducing those changes in the future.
+def py_cc_toolchain(**kwargs):
+ """Creates a py_cc_toolchain target.
+
+ Args:
+ **kwargs: Keyword args to pass onto underlying rule.
+ """
+
+ # This tag is added to easily identify usages through other macros.
+ add_tag(kwargs, "@rules_python//python:py_cc_toolchain")
+ _py_cc_toolchain(**kwargs)
diff --git a/python/private/py_cc_toolchain_rule.bzl b/python/private/py_cc_toolchain_rule.bzl
new file mode 100644
index 0000000..c80f845
--- /dev/null
+++ b/python/private/py_cc_toolchain_rule.bzl
@@ -0,0 +1,57 @@
+# 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.
+
+"""Implementation of py_cc_toolchain rule.
+
+NOTE: This is a beta-quality feature. APIs subject to change until
+https://github.com/bazelbuild/rules_python/issues/824 is considered done.
+"""
+
+load(":py_cc_toolchain_info.bzl", "PyCcToolchainInfo")
+
+def _py_cc_toolchain_impl(ctx):
+ py_cc_toolchain = PyCcToolchainInfo(
+ headers = struct(
+ providers_map = {
+ "CcInfo": ctx.attr.headers[CcInfo],
+ "DefaultInfo": ctx.attr.headers[DefaultInfo],
+ },
+ ),
+ python_version = ctx.attr.python_version,
+ )
+ return [platform_common.ToolchainInfo(
+ py_cc_toolchain = py_cc_toolchain,
+ )]
+
+py_cc_toolchain = rule(
+ implementation = _py_cc_toolchain_impl,
+ attrs = {
+ "headers": attr.label(
+ doc = ("Target that provides the Python headers. Typically this " +
+ "is a cc_library target."),
+ providers = [CcInfo],
+ mandatory = True,
+ ),
+ "python_version": attr.string(
+ doc = "The Major.minor Python version, e.g. 3.11",
+ mandatory = True,
+ ),
+ },
+ doc = """\
+A toolchain for a Python runtime's C/C++ information (e.g. headers)
+
+This rule carries information about the C/C++ side of a Python runtime, e.g.
+headers, shared libraries, etc.
+""",
+)
diff --git a/python/private/py_package.bzl b/python/private/py_package.bzl
new file mode 100644
index 0000000..08f4b0b
--- /dev/null
+++ b/python/private/py_package.bzl
@@ -0,0 +1,75 @@
+# 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.
+
+"Implementation of py_package rule"
+
+def _path_inside_wheel(input_file):
+ # input_file.short_path is sometimes relative ("../${repository_root}/foobar")
+ # which is not a valid path within a zip file. Fix that.
+ short_path = input_file.short_path
+ if short_path.startswith("..") and len(short_path) >= 3:
+ # Path separator. '/' on linux.
+ separator = short_path[2]
+
+ # Consume '../' part.
+ short_path = short_path[3:]
+
+ # Find position of next '/' and consume everything up to that character.
+ pos = short_path.find(separator)
+ short_path = short_path[pos + 1:]
+ return short_path
+
+def _py_package_impl(ctx):
+ inputs = depset(
+ transitive = [dep[DefaultInfo].data_runfiles.files for dep in ctx.attr.deps] +
+ [dep[DefaultInfo].default_runfiles.files for dep in ctx.attr.deps],
+ )
+
+ # TODO: '/' is wrong on windows, but the path separator is not available in starlark.
+ # Fix this once ctx.configuration has directory separator information.
+ packages = [p.replace(".", "/") for p in ctx.attr.packages]
+ if not packages:
+ filtered_inputs = inputs
+ else:
+ filtered_files = []
+
+ # TODO: flattening depset to list gives poor performance,
+ for input_file in inputs.to_list():
+ wheel_path = _path_inside_wheel(input_file)
+ for package in packages:
+ if wheel_path.startswith(package):
+ filtered_files.append(input_file)
+ filtered_inputs = depset(direct = filtered_files)
+
+ return [DefaultInfo(
+ files = filtered_inputs,
+ )]
+
+py_package_lib = struct(
+ implementation = _py_package_impl,
+ attrs = {
+ "deps": attr.label_list(
+ doc = "",
+ ),
+ "packages": attr.string_list(
+ mandatory = False,
+ allow_empty = True,
+ doc = """\
+List of Python packages to include in the distribution.
+Sub-packages are automatically included.
+""",
+ ),
+ },
+ path_inside_wheel = _path_inside_wheel,
+)
diff --git a/python/private/py_wheel.bzl b/python/private/py_wheel.bzl
new file mode 100644
index 0000000..d8bceab
--- /dev/null
+++ b/python/private/py_wheel.bzl
@@ -0,0 +1,442 @@
+# 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.
+
+"Implementation of py_wheel rule"
+
+load("//python/private:stamp.bzl", "is_stamping_enabled")
+load(":py_package.bzl", "py_package_lib")
+
+PyWheelInfo = provider(
+ doc = "Information about a wheel produced by `py_wheel`",
+ fields = {
+ "name_file": (
+ "File: A file containing the canonical name of the wheel (after " +
+ "stamping, if enabled)."
+ ),
+ "wheel": "File: The wheel file itself.",
+ },
+)
+
+_distribution_attrs = {
+ "abi": attr.string(
+ default = "none",
+ doc = "Python ABI tag. 'none' for pure-Python wheels.",
+ ),
+ "distribution": attr.string(
+ mandatory = True,
+ doc = """\
+Name of the distribution.
+
+This should match the project name onm PyPI. It's also the name that is used to
+refer to the package in other packages' dependencies.
+
+Workspace status keys are expanded using `{NAME}` format, for example:
+ - `distribution = "package.{CLASSIFIER}"`
+ - `distribution = "{DISTRIBUTION}"`
+
+For the available keys, see https://bazel.build/docs/user-manual#workspace-status
+""",
+ ),
+ "platform": attr.string(
+ default = "any",
+ doc = """\
+Supported platform. Use 'any' for pure-Python wheel.
+
+If you have included platform-specific data, such as a .pyd or .so
+extension module, you will need to specify the platform in standard
+pip format. If you support multiple platforms, you can define
+platform constraints, then use a select() to specify the appropriate
+specifier, eg:
+
+`
+platform = select({
+ "//platforms:windows_x86_64": "win_amd64",
+ "//platforms:macos_x86_64": "macosx_10_7_x86_64",
+ "//platforms:linux_x86_64": "manylinux2014_x86_64",
+})
+`
+""",
+ ),
+ "python_tag": attr.string(
+ default = "py3",
+ doc = "Supported Python version(s), eg `py3`, `cp35.cp36`, etc",
+ ),
+ "stamp": attr.int(
+ doc = """\
+Whether to encode build information into the wheel. Possible values:
+
+- `stamp = 1`: Always stamp the build information into the wheel, even in \
+[--nostamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) builds. \
+This setting should be avoided, since it potentially kills remote caching for the target and \
+any downstream actions that depend on it.
+
+- `stamp = 0`: Always replace build information by constant values. This gives good build result caching.
+
+- `stamp = -1`: Embedding of build information is controlled by the \
+[--[no]stamp](https://docs.bazel.build/versions/main/user-manual.html#flag--stamp) flag.
+
+Stamped targets are not rebuilt unless their dependencies change.
+ """,
+ default = -1,
+ values = [1, 0, -1],
+ ),
+ "version": attr.string(
+ mandatory = True,
+ doc = """\
+Version number of the package.
+
+Note that this attribute supports stamp format strings as well as 'make variables'.
+For example:
+ - `version = "1.2.3-{BUILD_TIMESTAMP}"`
+ - `version = "{BUILD_EMBED_LABEL}"`
+ - `version = "$(VERSION)"`
+
+Note that Bazel's output filename cannot include the stamp information, as outputs must be known
+during the analysis phase and the stamp data is available only during the action execution.
+
+The [`py_wheel`](/docs/packaging.md#py_wheel) macro produces a `.dist`-suffix target which creates a
+`dist/` folder containing the wheel with the stamped name, suitable for publishing.
+
+See [`py_wheel_dist`](/docs/packaging.md#py_wheel_dist) for more info.
+""",
+ ),
+ "_stamp_flag": attr.label(
+ doc = "A setting used to determine whether or not the `--stamp` flag is enabled",
+ default = Label("//python/private:stamp"),
+ ),
+}
+
+_requirement_attrs = {
+ "extra_requires": attr.string_list_dict(
+ doc = "List of optional requirements for this package",
+ ),
+ "requires": attr.string_list(
+ doc = ("List of requirements for this package. See the section on " +
+ "[Declaring required dependency](https://setuptools.readthedocs.io/en/latest/userguide/dependency_management.html#declaring-dependencies) " +
+ "for details and examples of the format of this argument."),
+ ),
+}
+
+_entrypoint_attrs = {
+ "console_scripts": attr.string_dict(
+ doc = """\
+Deprecated console_script entry points, e.g. `{'main': 'examples.wheel.main:main'}`.
+
+Deprecated: prefer the `entry_points` attribute, which supports `console_scripts` as well as other entry points.
+""",
+ ),
+ "entry_points": attr.string_list_dict(
+ doc = """\
+entry_points, e.g. `{'console_scripts': ['main = examples.wheel.main:main']}`.
+""",
+ ),
+}
+
+_other_attrs = {
+ "author": attr.string(
+ doc = "A string specifying the author of the package.",
+ default = "",
+ ),
+ "author_email": attr.string(
+ doc = "A string specifying the email address of the package author.",
+ default = "",
+ ),
+ "classifiers": attr.string_list(
+ doc = "A list of strings describing the categories for the package. For valid classifiers see https://pypi.org/classifiers",
+ ),
+ "description_content_type": attr.string(
+ doc = ("The type of contents in description_file. " +
+ "If not provided, the type will be inferred from the extension of description_file. " +
+ "Also see https://packaging.python.org/en/latest/specifications/core-metadata/#description-content-type"),
+ ),
+ "description_file": attr.label(
+ doc = "A file containing text describing the package.",
+ allow_single_file = True,
+ ),
+ "extra_distinfo_files": attr.label_keyed_string_dict(
+ doc = "Extra files to add to distinfo directory in the archive.",
+ allow_files = True,
+ ),
+ "homepage": attr.string(
+ doc = "A string specifying the URL for the package homepage.",
+ default = "",
+ ),
+ "license": attr.string(
+ doc = "A string specifying the license of the package.",
+ default = "",
+ ),
+ "project_urls": attr.string_dict(
+ doc = ("A string dict specifying additional browsable URLs for the project and corresponding labels, " +
+ "where label is the key and url is the value. " +
+ 'e.g `{{"Bug Tracker": "http://bitbucket.org/tarek/distribute/issues/"}}`'),
+ ),
+ "python_requires": attr.string(
+ doc = (
+ "Python versions required by this distribution, e.g. '>=3.5,<3.7'"
+ ),
+ default = "",
+ ),
+ "strip_path_prefixes": attr.string_list(
+ default = [],
+ doc = "path prefixes to strip from files added to the generated package",
+ ),
+ "summary": attr.string(
+ doc = "A one-line summary of what the distribution does",
+ ),
+}
+
+_PROJECT_URL_LABEL_LENGTH_LIMIT = 32
+_DESCRIPTION_FILE_EXTENSION_TO_TYPE = {
+ "md": "text/markdown",
+ "rst": "text/x-rst",
+}
+_DEFAULT_DESCRIPTION_FILE_TYPE = "text/plain"
+
+def _escape_filename_segment(segment):
+ """Escape a segment of the wheel filename.
+
+ See https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode
+ """
+
+ # TODO: this is wrong, isalnum replaces non-ascii letters, while we should
+ # not replace them.
+ # TODO: replace this with a regexp once starlark supports them.
+ escaped = ""
+ for character in segment.elems():
+ # isalnum doesn't handle unicode characters properly.
+ if character.isalnum() or character == ".":
+ escaped += character
+ elif not escaped.endswith("_"):
+ escaped += "_"
+ return escaped
+
+def _replace_make_variables(flag, ctx):
+ """Replace $(VERSION) etc make variables in flag"""
+ if "$" in flag:
+ for varname, varsub in ctx.var.items():
+ flag = flag.replace("$(%s)" % varname, varsub)
+ return flag
+
+def _input_file_to_arg(input_file):
+ """Converts a File object to string for --input_file argument to wheelmaker"""
+ return "%s;%s" % (py_package_lib.path_inside_wheel(input_file), input_file.path)
+
+def _py_wheel_impl(ctx):
+ abi = _replace_make_variables(ctx.attr.abi, ctx)
+ python_tag = _replace_make_variables(ctx.attr.python_tag, ctx)
+ version = _replace_make_variables(ctx.attr.version, ctx)
+
+ outfile = ctx.actions.declare_file("-".join([
+ _escape_filename_segment(ctx.attr.distribution),
+ _escape_filename_segment(version),
+ _escape_filename_segment(python_tag),
+ _escape_filename_segment(abi),
+ _escape_filename_segment(ctx.attr.platform),
+ ]) + ".whl")
+
+ name_file = ctx.actions.declare_file(ctx.label.name + ".name")
+
+ inputs_to_package = depset(
+ direct = ctx.files.deps,
+ )
+
+ # Inputs to this rule which are not to be packaged.
+ # Currently this is only the description file (if used).
+ other_inputs = []
+
+ # Wrap the inputs into a file to reduce command line length.
+ packageinputfile = ctx.actions.declare_file(ctx.attr.name + "_target_wrapped_inputs.txt")
+ content = ""
+ for input_file in inputs_to_package.to_list():
+ content += _input_file_to_arg(input_file) + "\n"
+ ctx.actions.write(output = packageinputfile, content = content)
+ other_inputs.append(packageinputfile)
+
+ args = ctx.actions.args()
+ args.add("--name", ctx.attr.distribution)
+ args.add("--version", version)
+ args.add("--python_tag", python_tag)
+ args.add("--abi", abi)
+ args.add("--platform", ctx.attr.platform)
+ args.add("--out", outfile)
+ args.add("--name_file", name_file)
+ args.add_all(ctx.attr.strip_path_prefixes, format_each = "--strip_path_prefix=%s")
+
+ # Pass workspace status files if stamping is enabled
+ if is_stamping_enabled(ctx.attr):
+ args.add("--volatile_status_file", ctx.version_file)
+ args.add("--stable_status_file", ctx.info_file)
+ other_inputs.extend([ctx.version_file, ctx.info_file])
+
+ args.add("--input_file_list", packageinputfile)
+
+ # Note: Description file and version are not embedded into metadata.txt yet,
+ # it will be done later by wheelmaker script.
+ metadata_file = ctx.actions.declare_file(ctx.attr.name + ".metadata.txt")
+ metadata_contents = ["Metadata-Version: 2.1"]
+ metadata_contents.append("Name: %s" % ctx.attr.distribution)
+
+ if ctx.attr.author:
+ metadata_contents.append("Author: %s" % ctx.attr.author)
+ if ctx.attr.author_email:
+ metadata_contents.append("Author-email: %s" % ctx.attr.author_email)
+ if ctx.attr.homepage:
+ metadata_contents.append("Home-page: %s" % ctx.attr.homepage)
+ if ctx.attr.license:
+ metadata_contents.append("License: %s" % ctx.attr.license)
+ if ctx.attr.description_content_type:
+ metadata_contents.append("Description-Content-Type: %s" % ctx.attr.description_content_type)
+ elif ctx.attr.description_file:
+ # infer the content type from description file extension.
+ description_file_type = _DESCRIPTION_FILE_EXTENSION_TO_TYPE.get(
+ ctx.file.description_file.extension,
+ _DEFAULT_DESCRIPTION_FILE_TYPE,
+ )
+ metadata_contents.append("Description-Content-Type: %s" % description_file_type)
+ if ctx.attr.summary:
+ metadata_contents.append("Summary: %s" % ctx.attr.summary)
+
+ for label, url in sorted(ctx.attr.project_urls.items()):
+ if len(label) > _PROJECT_URL_LABEL_LENGTH_LIMIT:
+ fail("`label` {} in `project_urls` is too long. It is limited to {} characters.".format(len(label), _PROJECT_URL_LABEL_LENGTH_LIMIT))
+ metadata_contents.append("Project-URL: %s, %s" % (label, url))
+
+ for c in ctx.attr.classifiers:
+ metadata_contents.append("Classifier: %s" % c)
+
+ if ctx.attr.python_requires:
+ metadata_contents.append("Requires-Python: %s" % ctx.attr.python_requires)
+ for requirement in ctx.attr.requires:
+ metadata_contents.append("Requires-Dist: %s" % requirement)
+
+ for option, option_requirements in sorted(ctx.attr.extra_requires.items()):
+ metadata_contents.append("Provides-Extra: %s" % option)
+ for requirement in option_requirements:
+ metadata_contents.append(
+ "Requires-Dist: %s; extra == '%s'" % (requirement, option),
+ )
+ ctx.actions.write(
+ output = metadata_file,
+ content = "\n".join(metadata_contents) + "\n",
+ )
+ other_inputs.append(metadata_file)
+ args.add("--metadata_file", metadata_file)
+
+ # Merge console_scripts into entry_points.
+ entrypoints = dict(ctx.attr.entry_points) # Copy so we can mutate it
+ if ctx.attr.console_scripts:
+ # Copy a console_scripts group that may already exist, so we can mutate it.
+ console_scripts = list(entrypoints.get("console_scripts", []))
+ entrypoints["console_scripts"] = console_scripts
+ for name, ref in ctx.attr.console_scripts.items():
+ console_scripts.append("{name} = {ref}".format(name = name, ref = ref))
+
+ # If any entry_points are provided, construct the file here and add it to the files to be packaged.
+ # see: https://packaging.python.org/specifications/entry-points/
+ if entrypoints:
+ lines = []
+ for group, entries in sorted(entrypoints.items()):
+ if lines:
+ # Blank line between groups
+ lines.append("")
+ lines.append("[{group}]".format(group = group))
+ lines += sorted(entries)
+ entry_points_file = ctx.actions.declare_file(ctx.attr.name + "_entry_points.txt")
+ content = "\n".join(lines)
+ ctx.actions.write(output = entry_points_file, content = content)
+ other_inputs.append(entry_points_file)
+ args.add("--entry_points_file", entry_points_file)
+
+ if ctx.attr.description_file:
+ description_file = ctx.file.description_file
+ args.add("--description_file", description_file)
+ other_inputs.append(description_file)
+
+ for target, filename in ctx.attr.extra_distinfo_files.items():
+ target_files = target.files.to_list()
+ if len(target_files) != 1:
+ fail(
+ "Multi-file target listed in extra_distinfo_files %s",
+ filename,
+ )
+ other_inputs.extend(target_files)
+ args.add(
+ "--extra_distinfo_file",
+ filename + ";" + target_files[0].path,
+ )
+
+ ctx.actions.run(
+ inputs = depset(direct = other_inputs, transitive = [inputs_to_package]),
+ outputs = [outfile, name_file],
+ arguments = [args],
+ executable = ctx.executable._wheelmaker,
+ progress_message = "Building wheel {}".format(ctx.label),
+ )
+ return [
+ DefaultInfo(
+ files = depset([outfile]),
+ runfiles = ctx.runfiles(files = [outfile]),
+ ),
+ PyWheelInfo(
+ wheel = outfile,
+ name_file = name_file,
+ ),
+ ]
+
+def _concat_dicts(*dicts):
+ result = {}
+ for d in dicts:
+ result.update(d)
+ return result
+
+py_wheel_lib = struct(
+ implementation = _py_wheel_impl,
+ attrs = _concat_dicts(
+ {
+ "deps": attr.label_list(
+ doc = """\
+Targets to be included in the distribution.
+
+The targets to package are usually `py_library` rules or filesets (for packaging data files).
+
+Note it's usually better to package `py_library` targets and use
+`entry_points` attribute to specify `console_scripts` than to package
+`py_binary` rules. `py_binary` targets would wrap a executable script that
+tries to locate `.runfiles` directory which is not packaged in the wheel.
+""",
+ ),
+ "_wheelmaker": attr.label(
+ executable = True,
+ cfg = "exec",
+ default = "//tools:wheelmaker",
+ ),
+ },
+ _distribution_attrs,
+ _requirement_attrs,
+ _entrypoint_attrs,
+ _other_attrs,
+ ),
+)
+
+py_wheel = rule(
+ implementation = py_wheel_lib.implementation,
+ doc = """\
+Internal rule used by the [py_wheel macro](/docs/packaging.md#py_wheel).
+
+These intentionally have the same name to avoid sharp edges with Bazel macros.
+For example, a `bazel query` for a user's `py_wheel` macro expands to `py_wheel` targets,
+in the way they expect.
+""",
+ attrs = py_wheel_lib.attrs,
+)
diff --git a/python/private/reexports.bzl b/python/private/reexports.bzl
new file mode 100644
index 0000000..a300a20
--- /dev/null
+++ b/python/private/reexports.bzl
@@ -0,0 +1,47 @@
+# Copyright 2019 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.
+
+"""Internal re-exports of built-in symbols.
+
+Currently the definitions here are re-exports of the native rules, "blessed" to
+work under `--incompatible_load_python_rules_from_bzl`. As the native rules get
+migrated to Starlark, their implementations will be removed from here.
+
+We want to re-export a built-in symbol as if it were defined in a Starlark
+file, so that users can for instance do:
+
+```
+load("@rules_python//python:defs.bzl", "PyInfo")
+```
+
+Unfortunately, we can't just write in defs.bzl
+
+```
+PyInfo = PyInfo
+```
+
+because the declaration of module-level symbol `PyInfo` makes the builtin
+inaccessible. So instead we access the builtin here and export it under a
+different name. Then we can load it from defs.bzl and export it there under
+the original name.
+"""
+
+# Don't use underscore prefix, since that would make the symbol local to this
+# file only. Use a non-conventional name to emphasize that this is not a public
+# symbol.
+# buildifier: disable=name-conventions
+internal_PyInfo = PyInfo
+
+# buildifier: disable=name-conventions
+internal_PyRuntimeInfo = PyRuntimeInfo
diff --git a/python/private/stamp.bzl b/python/private/stamp.bzl
new file mode 100644
index 0000000..6bc0cd9
--- /dev/null
+++ b/python/private/stamp.bzl
@@ -0,0 +1,87 @@
+# 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.
+
+"""A small utility module dedicated to detecting whether or not the `--stamp` flag is enabled
+
+This module can be removed likely after the following PRs ar addressed:
+- https://github.com/bazelbuild/bazel/issues/11164
+"""
+
+StampSettingInfo = provider(
+ doc = "Information about the `--stamp` command line flag",
+ fields = {
+ "value": "bool: Whether or not the `--stamp` flag was enabled",
+ },
+)
+
+def _stamp_build_setting_impl(ctx):
+ return StampSettingInfo(value = ctx.attr.value)
+
+_stamp_build_setting = rule(
+ doc = """\
+Whether to encode build information into the binary. Possible values:
+
+- stamp = 1: Always stamp the build information into the binary, even in [--nostamp][stamp] builds. \
+This setting should be avoided, since it potentially kills remote caching for the binary and \
+any downstream actions that depend on it.
+- stamp = 0: Always replace build information by constant values. This gives good build result caching.
+- stamp = -1: Embedding of build information is controlled by the [--[no]stamp][stamp] flag.
+
+Stamped binaries are not rebuilt unless their dependencies change.
+[stamp]: https://docs.bazel.build/versions/main/user-manual.html#flag--stamp
+ """,
+ implementation = _stamp_build_setting_impl,
+ attrs = {
+ "value": attr.bool(
+ doc = "The default value of the stamp build flag",
+ mandatory = True,
+ ),
+ },
+)
+
+def stamp_build_setting(name, visibility = ["//visibility:public"]):
+ native.config_setting(
+ name = "stamp_detect",
+ values = {"stamp": "1"},
+ visibility = visibility,
+ )
+
+ _stamp_build_setting(
+ name = name,
+ value = select({
+ ":stamp_detect": True,
+ "//conditions:default": False,
+ }),
+ visibility = visibility,
+ )
+
+def is_stamping_enabled(attr):
+ """Determine whether or not build stamping is enabled
+
+ Args:
+ attr (struct): A rule's struct of attributes (`ctx.attr`)
+
+ Returns:
+ bool: The stamp value
+ """
+ stamp_num = getattr(attr, "stamp", -1)
+ if stamp_num == 1:
+ return True
+ elif stamp_num == 0:
+ return False
+ elif stamp_num == -1:
+ stamp_flag = getattr(attr, "_stamp_flag", None)
+ return stamp_flag[StampSettingInfo].value if stamp_flag else False
+ else:
+ fail("Unexpected `stamp` value: {}".format(stamp_num))
diff --git a/python/private/toolchains_repo.bzl b/python/private/toolchains_repo.bzl
new file mode 100644
index 0000000..5923787
--- /dev/null
+++ b/python/private/toolchains_repo.bzl
@@ -0,0 +1,336 @@
+# Copyright 2022 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.
+
+"""Create a repository to hold the toolchains.
+
+This follows guidance here:
+https://docs.bazel.build/versions/main/skylark/deploying.html#registering-toolchains
+
+The "complex computation" in our case is simply downloading large artifacts.
+This guidance tells us how to avoid that: we put the toolchain targets in the
+alias repository with only the toolchain attribute pointing into the
+platform-specific repositories.
+"""
+
+load(
+ "//python:versions.bzl",
+ "LINUX_NAME",
+ "MACOS_NAME",
+ "PLATFORMS",
+ "WINDOWS_NAME",
+)
+
+def get_repository_name(repository_workspace):
+ dummy_label = "//:_"
+ return str(repository_workspace.relative(dummy_label))[:-len(dummy_label)] or "@"
+
+def python_toolchain_build_file_content(
+ prefix,
+ python_version,
+ set_python_version_constraint,
+ user_repository_name,
+ rules_python):
+ """Creates the content for toolchain definitions for a build file.
+
+ Args:
+ prefix: Python toolchain name prefixes
+ python_version: Python versions for the toolchains
+ set_python_version_constraint: string, "True" if the toolchain should
+ have the Python version constraint added as a requirement for
+ matching the toolchain, "False" if not.
+ user_repository_name: names for the user repos
+ rules_python: rules_python label
+
+ Returns:
+ build_content: Text containing toolchain definitions
+ """
+ if set_python_version_constraint == "True":
+ constraint = "{rules_python}//python/config_settings:is_python_{python_version}".format(
+ rules_python = rules_python,
+ python_version = python_version,
+ )
+ target_settings = '["{}"]'.format(constraint)
+ elif set_python_version_constraint == "False":
+ target_settings = "[]"
+ else:
+ fail(("Invalid set_python_version_constraint value: got {} {}, wanted " +
+ "either the string 'True' or the string 'False'; " +
+ "(did you convert bool to string?)").format(
+ type(set_python_version_constraint),
+ repr(set_python_version_constraint),
+ ))
+
+ # We create a list of toolchain content from iterating over
+ # the enumeration of PLATFORMS. We enumerate PLATFORMS in
+ # order to get us an index to increment the increment.
+ return "".join([
+ """
+toolchain(
+ name = "{prefix}{platform}_toolchain",
+ target_compatible_with = {compatible_with},
+ target_settings = {target_settings},
+ toolchain = "@{user_repository_name}_{platform}//:python_runtimes",
+ toolchain_type = "@bazel_tools//tools/python:toolchain_type",
+)
+
+toolchain(
+ name = "{prefix}{platform}_py_cc_toolchain",
+ target_compatible_with = {compatible_with},
+ target_settings = {target_settings},
+ toolchain = "@{user_repository_name}_{platform}//:py_cc_toolchain",
+ toolchain_type = "@rules_python//python/cc:toolchain_type",
+
+)
+""".format(
+ compatible_with = meta.compatible_with,
+ platform = platform,
+ # We have to use a String value here because bzlmod is passing in a
+ # string as we cannot have list of bools in build rule attribues.
+ # This if statement does not appear to work unless it is in the
+ # toolchain file.
+ target_settings = target_settings,
+ user_repository_name = user_repository_name,
+ prefix = prefix,
+ )
+ for platform, meta in PLATFORMS.items()
+ ])
+
+def _toolchains_repo_impl(rctx):
+ build_content = """\
+# Generated by python/private/toolchains_repo.bzl
+#
+# These can be registered in the workspace file or passed to --extra_toolchains
+# flag. By default all these toolchains are registered by the
+# python_register_toolchains macro so you don't normally need to interact with
+# these targets.
+
+"""
+
+ # Get the repository name
+ rules_python = get_repository_name(rctx.attr._rules_python_workspace)
+
+ toolchains = python_toolchain_build_file_content(
+ prefix = "",
+ python_version = rctx.attr.python_version,
+ set_python_version_constraint = str(rctx.attr.set_python_version_constraint),
+ user_repository_name = rctx.attr.user_repository_name,
+ rules_python = rules_python,
+ )
+
+ rctx.file("BUILD.bazel", build_content + toolchains)
+
+toolchains_repo = repository_rule(
+ _toolchains_repo_impl,
+ doc = "Creates a repository with toolchain definitions for all known platforms " +
+ "which can be registered or selected.",
+ attrs = {
+ "python_version": attr.string(doc = "The Python version."),
+ "set_python_version_constraint": attr.bool(doc = "if target_compatible_with for the toolchain should set the version constraint"),
+ "user_repository_name": attr.string(doc = "what the user chose for the base name"),
+ "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
+ },
+)
+
+def _toolchain_aliases_impl(rctx):
+ (os_name, arch) = get_host_os_arch(rctx)
+
+ host_platform = get_host_platform(os_name, arch)
+
+ is_windows = (os_name == WINDOWS_NAME)
+ python3_binary_path = "python.exe" if is_windows else "bin/python3"
+
+ # Base BUILD file for this repository.
+ build_contents = """\
+# Generated by python/private/toolchains_repo.bzl
+package(default_visibility = ["//visibility:public"])
+load("@rules_python//python:versions.bzl", "PLATFORMS", "gen_python_config_settings")
+gen_python_config_settings()
+exports_files(["defs.bzl"])
+alias(name = "files", actual = select({{":" + item: "@{py_repository}_" + item + "//:files" for item in PLATFORMS.keys()}}))
+alias(name = "includes", actual = select({{":" + item: "@{py_repository}_" + item + "//:includes" for item in PLATFORMS.keys()}}))
+alias(name = "libpython", actual = select({{":" + item: "@{py_repository}_" + item + "//:libpython" for item in PLATFORMS.keys()}}))
+alias(name = "py3_runtime", actual = select({{":" + item: "@{py_repository}_" + item + "//:py3_runtime" for item in PLATFORMS.keys()}}))
+alias(name = "python_headers", actual = select({{":" + item: "@{py_repository}_" + item + "//:python_headers" for item in PLATFORMS.keys()}}))
+alias(name = "python_runtimes", actual = select({{":" + item: "@{py_repository}_" + item + "//:python_runtimes" for item in PLATFORMS.keys()}}))
+alias(name = "python3", actual = select({{":" + item: "@{py_repository}_" + item + "//:" + ("python.exe" if "windows" in item else "bin/python3") for item in PLATFORMS.keys()}}))
+""".format(
+ py_repository = rctx.attr.user_repository_name,
+ )
+ if not is_windows:
+ build_contents += """\
+alias(name = "pip", actual = select({{":" + item: "@{py_repository}_" + item + "//:python_runtimes" for item in PLATFORMS.keys() if "windows" not in item}}))
+""".format(
+ py_repository = rctx.attr.user_repository_name,
+ host_platform = host_platform,
+ )
+ rctx.file("BUILD.bazel", build_contents)
+
+ # Expose a Starlark file so rules can know what host platform we used and where to find an interpreter
+ # when using repository_ctx.path, which doesn't understand aliases.
+ rctx.file("defs.bzl", content = """\
+# Generated by python/private/toolchains_repo.bzl
+
+load("{rules_python}//python/config_settings:transition.bzl", _py_binary = "py_binary", _py_test = "py_test")
+load("{rules_python}//python:pip.bzl", _compile_pip_requirements = "compile_pip_requirements")
+
+host_platform = "{host_platform}"
+interpreter = "@{py_repository}_{host_platform}//:{python3_binary_path}"
+
+def py_binary(name, **kwargs):
+ return _py_binary(
+ name = name,
+ python_version = "{python_version}",
+ **kwargs
+ )
+
+def py_test(name, **kwargs):
+ return _py_test(
+ name = name,
+ python_version = "{python_version}",
+ **kwargs
+ )
+
+def compile_pip_requirements(name, **kwargs):
+ return _compile_pip_requirements(
+ name = name,
+ py_binary = py_binary,
+ py_test = py_test,
+ **kwargs
+ )
+
+""".format(
+ host_platform = host_platform,
+ py_repository = rctx.attr.user_repository_name,
+ python_version = rctx.attr.python_version,
+ python3_binary_path = python3_binary_path,
+ rules_python = get_repository_name(rctx.attr._rules_python_workspace),
+ ))
+
+toolchain_aliases = repository_rule(
+ _toolchain_aliases_impl,
+ doc = """Creates a repository with a shorter name meant for the host platform, which contains
+ a BUILD.bazel file declaring aliases to the host platform's targets.
+ """,
+ attrs = {
+ "python_version": attr.string(doc = "The Python version."),
+ "user_repository_name": attr.string(
+ mandatory = True,
+ doc = "The base name for all created repositories, like 'python38'.",
+ ),
+ "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
+ },
+)
+
+def _multi_toolchain_aliases_impl(rctx):
+ rules_python = rctx.attr._rules_python_workspace.workspace_name
+
+ for python_version, repository_name in rctx.attr.python_versions.items():
+ file = "{}/defs.bzl".format(python_version)
+ rctx.file(file, content = """\
+# Generated by python/private/toolchains_repo.bzl
+
+load(
+ "@{repository_name}//:defs.bzl",
+ _compile_pip_requirements = "compile_pip_requirements",
+ _host_platform = "host_platform",
+ _interpreter = "interpreter",
+ _py_binary = "py_binary",
+ _py_test = "py_test",
+)
+
+compile_pip_requirements = _compile_pip_requirements
+host_platform = _host_platform
+interpreter = _interpreter
+py_binary = _py_binary
+py_test = _py_test
+""".format(
+ repository_name = repository_name,
+ ))
+ rctx.file("{}/BUILD.bazel".format(python_version), "")
+
+ pip_bzl = """\
+# Generated by python/private/toolchains_repo.bzl
+
+load("@{rules_python}//python:pip.bzl", "pip_parse", _multi_pip_parse = "multi_pip_parse")
+
+def multi_pip_parse(name, requirements_lock, **kwargs):
+ return _multi_pip_parse(
+ name = name,
+ python_versions = {python_versions},
+ requirements_lock = requirements_lock,
+ **kwargs
+ )
+
+""".format(
+ python_versions = rctx.attr.python_versions.keys(),
+ rules_python = rules_python,
+ )
+ rctx.file("pip.bzl", content = pip_bzl)
+ rctx.file("BUILD.bazel", "")
+
+multi_toolchain_aliases = repository_rule(
+ _multi_toolchain_aliases_impl,
+ attrs = {
+ "python_versions": attr.string_dict(doc = "The Python versions."),
+ "_rules_python_workspace": attr.label(default = Label("//:WORKSPACE")),
+ },
+)
+
+def sanitize_platform_name(platform):
+ return platform.replace("-", "_")
+
+def get_host_platform(os_name, arch):
+ """Gets the host platform.
+
+ Args:
+ os_name: the host OS name.
+ arch: the host arch.
+ Returns:
+ The host platform.
+ """
+ host_platform = None
+ for platform, meta in PLATFORMS.items():
+ if meta.os_name == os_name and meta.arch == arch:
+ host_platform = platform
+ if not host_platform:
+ fail("No platform declared for host OS {} on arch {}".format(os_name, arch))
+ return host_platform
+
+def get_host_os_arch(rctx):
+ """Infer the host OS name and arch from a repository context.
+
+ Args:
+ rctx: Bazel's repository_ctx.
+ Returns:
+ A tuple with the host OS name and arch.
+ """
+ os_name = rctx.os.name
+
+ # We assume the arch for Windows is always x86_64.
+ if "windows" in os_name.lower():
+ arch = "x86_64"
+
+ # Normalize the os_name. E.g. os_name could be "OS windows server 2019".
+ os_name = WINDOWS_NAME
+ else:
+ # This is not ideal, but bazel doesn't directly expose arch.
+ arch = rctx.execute(["uname", "-m"]).stdout.strip()
+
+ # Normalize the os_name.
+ if "mac" in os_name.lower():
+ os_name = MACOS_NAME
+ elif "linux" in os_name.lower():
+ os_name = LINUX_NAME
+
+ return (os_name, arch)
diff --git a/python/private/util.bzl b/python/private/util.bzl
new file mode 100644
index 0000000..6c8761d
--- /dev/null
+++ b/python/private/util.bzl
@@ -0,0 +1,85 @@
+# 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.
+
+"""Functionality shared by multiple pieces of code."""
+
+load("@bazel_skylib//lib:types.bzl", "types")
+
+def copy_propagating_kwargs(from_kwargs, into_kwargs = None):
+ """Copies args that must be compatible between two targets with a dependency relationship.
+
+ This is intended for when one target depends on another, so they must have
+ compatible settings such as `testonly` and `compatible_with`. This usually
+ happens when a macro generates multiple targets, some of which depend
+ on one another, so their settings must be compatible.
+
+ Args:
+ from_kwargs: keyword args dict whose common kwarg will be copied.
+ into_kwargs: optional keyword args dict that the values from `from_kwargs`
+ will be copied into. The values in this dict will take precedence
+ over the ones in `from_kwargs` (i.e., if this has `testonly` already
+ set, then it won't be overwritten).
+ NOTE: THIS WILL BE MODIFIED IN-PLACE.
+
+ Returns:
+ Keyword args to use for the depender target derived from the dependency
+ target. If `into_kwargs` was passed in, then that same object is
+ returned; this is to facilitate easy `**` expansion.
+ """
+ if into_kwargs == None:
+ into_kwargs = {}
+
+ # Include tags because people generally expect tags to propagate.
+ for attr in ("testonly", "tags", "compatible_with", "restricted_to"):
+ if attr in from_kwargs and attr not in into_kwargs:
+ into_kwargs[attr] = from_kwargs[attr]
+ return into_kwargs
+
+# The implementation of the macros and tagging mechanism follows the example
+# set by rules_cc and rules_java.
+
+_MIGRATION_TAG = "__PYTHON_RULES_MIGRATION_DO_NOT_USE_WILL_BREAK__"
+
+def add_migration_tag(attrs):
+ """Add a special tag to `attrs` to aid migration off native rles.
+
+ Args:
+ attrs: dict of keyword args. The `tags` key will be modified in-place.
+
+ Returns:
+ The same `attrs` object, but modified.
+ """
+ add_tag(attrs, _MIGRATION_TAG)
+ return attrs
+
+def add_tag(attrs, tag):
+ """Adds `tag` to `attrs["tags"]`.
+
+ Args:
+ attrs: dict of keyword args. It is modified in place.
+ tag: str, the tag to add.
+ """
+ if "tags" in attrs and attrs["tags"] != None:
+ tags = attrs["tags"]
+
+ # Preserve the input type: this allows a test verifying the underlying
+ # rule can accept the tuple for the tags argument.
+ if types.is_tuple(tags):
+ attrs["tags"] = tags + (tag,)
+ else:
+ # List concatenation is necessary because the original value
+ # may be a frozen list.
+ attrs["tags"] = tags + [tag]
+ else:
+ attrs["tags"] = [tag]
diff --git a/python/private/version_label.bzl b/python/private/version_label.bzl
new file mode 100644
index 0000000..1bca92c
--- /dev/null
+++ b/python/private/version_label.bzl
@@ -0,0 +1,36 @@
+# 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.
+
+""
+
+def version_label(version, *, sep = ""):
+ """A version fragment derived from python minor version
+
+ Examples:
+ version_label("3.9") == "39"
+ version_label("3.9.12", sep="_") == "3_9"
+ version_label("3.11") == "311"
+
+ Args:
+ version: Python version.
+ sep: The separator between major and minor version numbers, defaults
+ to an empty string.
+
+ Returns:
+ The fragment of the version.
+ """
+ major, _, version = version.partition(".")
+ minor, _, _ = version.partition(".")
+
+ return major + sep + minor
diff --git a/python/proto.bzl b/python/proto.bzl
new file mode 100644
index 0000000..3f455ae
--- /dev/null
+++ b/python/proto.bzl
@@ -0,0 +1,21 @@
+# Copyright 2022 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.
+
+"""
+Python proto library.
+"""
+
+load("//python/private/proto:py_proto_library.bzl", _py_proto_library = "py_proto_library")
+
+py_proto_library = _py_proto_library
diff --git a/python/py_binary.bzl b/python/py_binary.bzl
new file mode 100644
index 0000000..6b6f7e0
--- /dev/null
+++ b/python/py_binary.bzl
@@ -0,0 +1,31 @@
+# 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.
+
+"""Public entry point for py_binary."""
+
+load("//python/private:util.bzl", "add_migration_tag")
+
+def py_binary(**attrs):
+ """See the Bazel core [py_binary](https://docs.bazel.build/versions/master/be/python.html#py_binary) documentation.
+
+ Args:
+ **attrs: Rule attributes
+ """
+ if attrs.get("python_version") == "PY2":
+ fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886")
+ if attrs.get("srcs_version") in ("PY2", "PY2ONLY"):
+ fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886")
+
+ # buildifier: disable=native-python
+ native.py_binary(**add_migration_tag(attrs))
diff --git a/python/py_cc_link_params_info.bzl b/python/py_cc_link_params_info.bzl
new file mode 100644
index 0000000..0ebd64b
--- /dev/null
+++ b/python/py_cc_link_params_info.bzl
@@ -0,0 +1,3 @@
+"""Public entry point for PyCcLinkParamsInfo."""
+
+PyCcLinkParamsInfo = PyCcLinkParamsProvider
diff --git a/python/py_import.bzl b/python/py_import.bzl
new file mode 100644
index 0000000..c928412
--- /dev/null
+++ b/python/py_import.bzl
@@ -0,0 +1,67 @@
+# 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.
+
+"""Public entry point for py_import rule."""
+
+load(":py_info.bzl", "PyInfo")
+
+def _py_import_impl(ctx):
+ # See https://github.com/bazelbuild/bazel/blob/0.24.0/src/main/java/com/google/devtools/build/lib/bazel/rules/python/BazelPythonSemantics.java#L104 .
+ import_paths = [
+ "/".join([ctx.workspace_name, x.short_path])
+ for x in ctx.files.srcs
+ ]
+
+ return [
+ DefaultInfo(
+ default_runfiles = ctx.runfiles(ctx.files.srcs, collect_default = True),
+ ),
+ PyInfo(
+ transitive_sources = depset(transitive = [
+ dep[PyInfo].transitive_sources
+ for dep in ctx.attr.deps
+ ]),
+ imports = depset(direct = import_paths, transitive = [
+ dep[PyInfo].imports
+ for dep in ctx.attr.deps
+ ]),
+ ),
+ ]
+
+py_import = rule(
+ doc = """This rule allows the use of Python packages as dependencies.
+
+ It imports the given `.egg` file(s), which might be checked in source files,
+ fetched externally as with `http_file`, or produced as outputs of other rules.
+
+ It may be used like a `py_library`, in the `deps` of other Python rules.
+
+ This is similar to [java_import](https://docs.bazel.build/versions/master/be/java.html#java_import).
+ """,
+ implementation = _py_import_impl,
+ attrs = {
+ "deps": attr.label_list(
+ doc = "The list of other libraries to be linked in to the " +
+ "binary target.",
+ providers = [PyInfo],
+ ),
+ "srcs": attr.label_list(
+ doc = "The list of Python package files provided to Python targets " +
+ "that depend on this target. Note that currently only the .egg " +
+ "format is accepted. For .whl files, try the whl_library rule. " +
+ "We accept contributions to extend py_import to handle .whl.",
+ allow_files = [".egg"],
+ ),
+ },
+)
diff --git a/python/py_info.bzl b/python/py_info.bzl
new file mode 100644
index 0000000..2c3997d
--- /dev/null
+++ b/python/py_info.bzl
@@ -0,0 +1,19 @@
+# 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.
+
+"""Public entry point for PyInfo."""
+
+load("//python/private:reexports.bzl", "internal_PyInfo")
+
+PyInfo = internal_PyInfo
diff --git a/python/py_library.bzl b/python/py_library.bzl
new file mode 100644
index 0000000..d54cbb2
--- /dev/null
+++ b/python/py_library.bzl
@@ -0,0 +1,29 @@
+# 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.
+
+"""Public entry point for py_library."""
+
+load("//python/private:util.bzl", "add_migration_tag")
+
+def py_library(**attrs):
+ """See the Bazel core [py_library](https://docs.bazel.build/versions/master/be/python.html#py_library) documentation.
+
+ Args:
+ **attrs: Rule attributes
+ """
+ if attrs.get("srcs_version") in ("PY2", "PY2ONLY"):
+ fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886")
+
+ # buildifier: disable=native-python
+ native.py_library(**add_migration_tag(attrs))
diff --git a/python/py_runtime.bzl b/python/py_runtime.bzl
new file mode 100644
index 0000000..b70f9d4
--- /dev/null
+++ b/python/py_runtime.bzl
@@ -0,0 +1,29 @@
+# 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.
+
+"""Public entry point for py_runtime."""
+
+load("//python/private:util.bzl", "add_migration_tag")
+
+def py_runtime(**attrs):
+ """See the Bazel core [py_runtime](https://docs.bazel.build/versions/master/be/python.html#py_runtime) documentation.
+
+ Args:
+ **attrs: Rule attributes
+ """
+ if attrs.get("python_version") == "PY2":
+ fail("Python 2 is no longer supported: see https://github.com/bazelbuild/rules_python/issues/886")
+
+ # buildifier: disable=native-python
+ native.py_runtime(**add_migration_tag(attrs))
diff --git a/python/py_runtime_info.bzl b/python/py_runtime_info.bzl
new file mode 100644
index 0000000..15598ee
--- /dev/null
+++ b/python/py_runtime_info.bzl
@@ -0,0 +1,19 @@
+# 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.
+
+"""Public entry point for PyRuntimeInfo."""
+
+load("//python/private:reexports.bzl", "internal_PyRuntimeInfo")
+
+PyRuntimeInfo = internal_PyRuntimeInfo
diff --git a/python/py_runtime_pair.bzl b/python/py_runtime_pair.bzl
new file mode 100644
index 0000000..951c606
--- /dev/null
+++ b/python/py_runtime_pair.bzl
@@ -0,0 +1,87 @@
+# 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.
+
+"""Public entry point for py_runtime_pair."""
+
+load("@bazel_tools//tools/python:toolchain.bzl", _py_runtime_pair = "py_runtime_pair")
+
+# NOTE: This doc is copy/pasted from the builtin py_runtime_pair rule so our
+# doc generator gives useful API docs.
+def py_runtime_pair(name, py2_runtime = None, py3_runtime = None, **attrs):
+ """A toolchain rule for Python.
+
+ This used to wrap up to two Python runtimes, one for Python 2 and one for Python 3.
+ However, Python 2 is no longer supported, so it now only wraps a single Python 3
+ runtime.
+
+ Usually the wrapped runtimes are declared using the `py_runtime` rule, but any
+ rule returning a `PyRuntimeInfo` provider may be used.
+
+ This rule returns a `platform_common.ToolchainInfo` provider with the following
+ schema:
+
+ ```python
+ platform_common.ToolchainInfo(
+ py2_runtime = None,
+ py3_runtime = <PyRuntimeInfo or None>,
+ )
+ ```
+
+ Example usage:
+
+ ```python
+ # In your BUILD file...
+
+ load("@rules_python//python:defs.bzl", "py_runtime_pair")
+
+ py_runtime(
+ name = "my_py3_runtime",
+ interpreter_path = "/system/python3",
+ python_version = "PY3",
+ )
+
+ py_runtime_pair(
+ name = "my_py_runtime_pair",
+ py3_runtime = ":my_py3_runtime",
+ )
+
+ toolchain(
+ name = "my_toolchain",
+ target_compatible_with = <...>,
+ toolchain = ":my_py_runtime_pair",
+ toolchain_type = "@rules_python//python:toolchain_type",
+ )
+ ```
+
+ ```python
+ # In your WORKSPACE...
+
+ register_toolchains("//my_pkg:my_toolchain")
+ ```
+
+ Args:
+ name: str, the name of the target
+ py2_runtime: optional Label; must be unset or None; an error is raised
+ otherwise.
+ py3_runtime: Label; a target with `PyRuntimeInfo` for Python 3.
+ **attrs: Extra attrs passed onto the native rule
+ """
+ if attrs.get("py2_runtime"):
+ fail("PYthon 2 is no longer supported: see https://github.com/bazelbuild/rules_python/issues/886")
+ _py_runtime_pair(
+ name = name,
+ py2_runtime = py2_runtime,
+ py3_runtime = py3_runtime,
+ **attrs
+ )
diff --git a/python/py_test.bzl b/python/py_test.bzl
new file mode 100644
index 0000000..09580c0
--- /dev/null
+++ b/python/py_test.bzl
@@ -0,0 +1,31 @@
+# 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.
+
+"""Public entry point for py_test."""
+
+load("//python/private:util.bzl", "add_migration_tag")
+
+def py_test(**attrs):
+ """See the Bazel core [py_test](https://docs.bazel.build/versions/master/be/python.html#py_test) documentation.
+
+ Args:
+ **attrs: Rule attributes
+ """
+ if attrs.get("python_version") == "PY2":
+ fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886")
+ if attrs.get("srcs_version") in ("PY2", "PY2ONLY"):
+ fail("Python 2 is no longer supported: https://github.com/bazelbuild/rules_python/issues/886")
+
+ # buildifier: disable=native-python
+ native.py_test(**add_migration_tag(attrs))
diff --git a/python/python.bzl b/python/python.bzl
new file mode 100644
index 0000000..3e739ca
--- /dev/null
+++ b/python/python.bzl
@@ -0,0 +1,67 @@
+# Copyright 2017 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.
+
+"""Re-exports for some of the core Bazel Python rules.
+
+This file is deprecated; please use the exports in defs.bzl instead. This is to
+follow the new naming convention of putting core rules for a language
+underneath @rules_<LANG>//<LANG>:defs.bzl. The exports in this file will be
+disallowed in a future Bazel release by
+`--incompatible_load_python_rules_from_bzl`.
+"""
+
+def py_library(*args, **kwargs):
+ """Deprecated py_library rule.
+
+ See the Bazel core [py_library](
+ https://docs.bazel.build/versions/master/be/python.html#py_library)
+ documentation.
+
+ Deprecated: This symbol will become unusuable when
+ `--incompatible_load_python_rules_from_bzl` is enabled. Please use the
+ symbols in `@rules_python//python:defs.bzl` instead.
+ """
+
+ # buildifier: disable=native-python
+ native.py_library(*args, **kwargs)
+
+def py_binary(*args, **kwargs):
+ """Deprecated py_binary rule.
+
+ See the Bazel core [py_binary](
+ https://docs.bazel.build/versions/master/be/python.html#py_binary)
+ documentation.
+
+ Deprecated: This symbol will become unusuable when
+ `--incompatible_load_python_rules_from_bzl` is enabled. Please use the
+ symbols in `@rules_python//python:defs.bzl` instead.
+ """
+
+ # buildifier: disable=native-python
+ native.py_binary(*args, **kwargs)
+
+def py_test(*args, **kwargs):
+ """Deprecated py_test rule.
+
+ See the Bazel core [py_test](
+ https://docs.bazel.build/versions/master/be/python.html#py_test)
+ documentation.
+
+ Deprecated: This symbol will become unusuable when
+ `--incompatible_load_python_rules_from_bzl` is enabled. Please use the
+ symbols in `@rules_python//python:defs.bzl` instead.
+ """
+
+ # buildifier: disable=native-python
+ native.py_test(*args, **kwargs)
diff --git a/python/repositories.bzl b/python/repositories.bzl
new file mode 100644
index 0000000..62d9421
--- /dev/null
+++ b/python/repositories.bzl
@@ -0,0 +1,637 @@
+# Copyright 2022 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.
+
+"""This file contains macros to be called during WORKSPACE evaluation.
+
+For historic reasons, pip_repositories() is defined in //python:pip.bzl.
+"""
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", _http_archive = "http_archive")
+load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe")
+load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
+load("//python/private:coverage_deps.bzl", "coverage_dep")
+load(
+ "//python/private:toolchains_repo.bzl",
+ "multi_toolchain_aliases",
+ "toolchain_aliases",
+ "toolchains_repo",
+)
+load(
+ ":versions.bzl",
+ "DEFAULT_RELEASE_BASE_URL",
+ "MINOR_MAPPING",
+ "PLATFORMS",
+ "TOOL_VERSIONS",
+ "get_release_info",
+)
+
+def http_archive(**kwargs):
+ maybe(_http_archive, **kwargs)
+
+def py_repositories():
+ """Runtime dependencies that users must install.
+
+ This function should be loaded and called in the user's WORKSPACE.
+ With bzlmod enabled, this function is not needed since MODULE.bazel handles transitive deps.
+ """
+ http_archive(
+ name = "bazel_skylib",
+ sha256 = "74d544d96f4a5bb630d465ca8bbcfe231e3594e5aae57e1edbf17a6eb3ca2506",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.3.0/bazel-skylib-1.3.0.tar.gz",
+ ],
+ )
+
+########
+# Remaining content of the file is only used to support toolchains.
+########
+
+STANDALONE_INTERPRETER_FILENAME = "STANDALONE_INTERPRETER"
+
+def get_interpreter_dirname(rctx, python_interpreter_target):
+ """Get a python interpreter target dirname.
+
+ Args:
+ rctx (repository_ctx): The repository rule's context object.
+ python_interpreter_target (Target): A target representing a python interpreter.
+
+ Returns:
+ str: The Python interpreter directory.
+ """
+
+ return rctx.path(Label("{}//:WORKSPACE".format(str(python_interpreter_target).split("//")[0]))).dirname
+
+def is_standalone_interpreter(rctx, python_interpreter_target):
+ """Query a python interpreter target for whether or not it's a rules_rust provided toolchain
+
+ Args:
+ rctx (repository_ctx): The repository rule's context object.
+ python_interpreter_target (Target): A target representing a python interpreter.
+
+ Returns:
+ bool: Whether or not the target is from a rules_python generated toolchain.
+ """
+
+ # Only update the location when using a hermetic toolchain.
+ if not python_interpreter_target:
+ return False
+
+ # This is a rules_python provided toolchain.
+ return rctx.execute([
+ "ls",
+ "{}/{}".format(
+ get_interpreter_dirname(rctx, python_interpreter_target),
+ STANDALONE_INTERPRETER_FILENAME,
+ ),
+ ]).return_code == 0
+
+def _python_repository_impl(rctx):
+ if rctx.attr.distutils and rctx.attr.distutils_content:
+ fail("Only one of (distutils, distutils_content) should be set.")
+ if bool(rctx.attr.url) == bool(rctx.attr.urls):
+ fail("Exactly one of (url, urls) must be set.")
+
+ platform = rctx.attr.platform
+ python_version = rctx.attr.python_version
+ python_short_version = python_version.rpartition(".")[0]
+ release_filename = rctx.attr.release_filename
+ urls = rctx.attr.urls or [rctx.attr.url]
+
+ if release_filename.endswith(".zst"):
+ rctx.download(
+ url = urls,
+ sha256 = rctx.attr.sha256,
+ output = release_filename,
+ )
+ unzstd = rctx.which("unzstd")
+ if not unzstd:
+ url = rctx.attr.zstd_url.format(version = rctx.attr.zstd_version)
+ rctx.download_and_extract(
+ url = url,
+ sha256 = rctx.attr.zstd_sha256,
+ )
+ working_directory = "zstd-{version}".format(version = rctx.attr.zstd_version)
+ make_result = rctx.execute(
+ ["make", "--jobs=4"],
+ timeout = 600,
+ quiet = True,
+ working_directory = working_directory,
+ )
+ if make_result.return_code:
+ fail_msg = (
+ "Failed to compile 'zstd' from source for use in Python interpreter extraction. " +
+ "'make' error message: {}".format(make_result.stderr)
+ )
+ fail(fail_msg)
+ zstd = "{working_directory}/zstd".format(working_directory = working_directory)
+ unzstd = "./unzstd"
+ rctx.symlink(zstd, unzstd)
+
+ exec_result = rctx.execute([
+ "tar",
+ "--extract",
+ "--strip-components=2",
+ "--use-compress-program={unzstd}".format(unzstd = unzstd),
+ "--file={}".format(release_filename),
+ ])
+ if exec_result.return_code:
+ fail_msg = (
+ "Failed to extract Python interpreter from '{}'. ".format(release_filename) +
+ "'tar' error message: {}".format(exec_result.stderr)
+ )
+ fail(fail_msg)
+ else:
+ rctx.download_and_extract(
+ url = urls,
+ sha256 = rctx.attr.sha256,
+ stripPrefix = rctx.attr.strip_prefix,
+ )
+
+ patches = rctx.attr.patches
+ if patches:
+ for patch in patches:
+ # Should take the strip as an attr, but this is fine for the moment
+ rctx.patch(patch, strip = 1)
+
+ # Write distutils.cfg to the Python installation.
+ if "windows" in rctx.os.name:
+ distutils_path = "Lib/distutils/distutils.cfg"
+ else:
+ distutils_path = "lib/python{}/distutils/distutils.cfg".format(python_short_version)
+ if rctx.attr.distutils:
+ rctx.file(distutils_path, rctx.read(rctx.attr.distutils))
+ elif rctx.attr.distutils_content:
+ rctx.file(distutils_path, rctx.attr.distutils_content)
+
+ # Make the Python installation read-only.
+ if not rctx.attr.ignore_root_user_error:
+ if "windows" not in rctx.os.name:
+ lib_dir = "lib" if "windows" not in platform else "Lib"
+ exec_result = rctx.execute(["chmod", "-R", "ugo-w", lib_dir])
+ if exec_result.return_code != 0:
+ fail_msg = "Failed to make interpreter installation read-only. 'chmod' error msg: {}".format(
+ exec_result.stderr,
+ )
+ fail(fail_msg)
+ exec_result = rctx.execute(["touch", "{}/.test".format(lib_dir)])
+ if exec_result.return_code == 0:
+ exec_result = rctx.execute(["id", "-u"])
+ if exec_result.return_code != 0:
+ fail("Could not determine current user ID. 'id -u' error msg: {}".format(
+ exec_result.stderr,
+ ))
+ uid = int(exec_result.stdout.strip())
+ if uid == 0:
+ fail("The current user is root, please run as non-root when using the hermetic Python interpreter. See https://github.com/bazelbuild/rules_python/pull/713.")
+ else:
+ fail("The current user has CAP_DAC_OVERRIDE set, please drop this capability when using the hermetic Python interpreter. See https://github.com/bazelbuild/rules_python/pull/713.")
+
+ python_bin = "python.exe" if ("windows" in platform) else "bin/python3"
+
+ glob_include = []
+ glob_exclude = [
+ "**/* *", # Bazel does not support spaces in file names.
+ # Unused shared libraries. `python` executable and the `:libpython` target
+ # depend on `libpython{python_version}.so.1.0`.
+ "lib/libpython{python_version}.so".format(python_version = python_short_version),
+ # static libraries
+ "lib/**/*.a",
+ # tests for the standard libraries.
+ "lib/python{python_version}/**/test/**".format(python_version = python_short_version),
+ "lib/python{python_version}/**/tests/**".format(python_version = python_short_version),
+ ]
+
+ if rctx.attr.ignore_root_user_error:
+ glob_exclude += [
+ # These pycache files are created on first use of the associated python files.
+ # Exclude them from the glob because otherwise between the first time and second time a python toolchain is used,"
+ # the definition of this filegroup will change, and depending rules will get invalidated."
+ # See https://github.com/bazelbuild/rules_python/issues/1008 for unconditionally adding these to toolchains so we can stop ignoring them."
+ "**/__pycache__/*.pyc",
+ "**/__pycache__/*.pyc.*", # During pyc creation, temp files named *.pyc.NNN are created
+ "**/__pycache__/*.pyo",
+ ]
+
+ if "windows" in platform:
+ glob_include += [
+ "*.exe",
+ "*.dll",
+ "bin/**",
+ "DLLs/**",
+ "extensions/**",
+ "include/**",
+ "Lib/**",
+ "libs/**",
+ "Scripts/**",
+ "share/**",
+ ]
+ else:
+ glob_include += [
+ "bin/**",
+ "extensions/**",
+ "include/**",
+ "lib/**",
+ "libs/**",
+ "share/**",
+ ]
+
+ if rctx.attr.coverage_tool:
+ if "windows" in rctx.os.name:
+ coverage_tool = None
+ else:
+ coverage_tool = '"{}"'.format(rctx.attr.coverage_tool)
+
+ coverage_attr_text = """\
+ coverage_tool = select({{
+ ":coverage_enabled": {coverage_tool},
+ "//conditions:default": None
+ }}),
+""".format(coverage_tool = coverage_tool)
+ else:
+ coverage_attr_text = " # coverage_tool attribute not supported by this Bazel version"
+
+ build_content = """\
+# Generated by python/repositories.bzl
+
+load("@bazel_tools//tools/python:toolchain.bzl", "py_runtime_pair")
+load("@rules_python//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
+
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+ name = "files",
+ srcs = glob(
+ include = {glob_include},
+ # Platform-agnostic filegroup can't match on all patterns.
+ allow_empty = True,
+ exclude = {glob_exclude},
+ ),
+)
+
+cc_import(
+ name = "interface",
+ interface_library = "libs/python{python_version_nodot}.lib",
+ system_provided = True,
+)
+
+filegroup(
+ name = "includes",
+ srcs = glob(["include/**/*.h"]),
+)
+
+cc_library(
+ name = "python_headers",
+ deps = select({{
+ "@bazel_tools//src/conditions:windows": [":interface"],
+ "//conditions:default": None,
+ }}),
+ hdrs = [":includes"],
+ includes = [
+ "include",
+ "include/python{python_version}",
+ "include/python{python_version}m",
+ ],
+)
+
+cc_library(
+ name = "libpython",
+ hdrs = [":includes"],
+ srcs = select({{
+ "@platforms//os:windows": ["python3.dll", "libs/python{python_version_nodot}.lib"],
+ "@platforms//os:macos": ["lib/libpython{python_version}.dylib"],
+ "@platforms//os:linux": ["lib/libpython{python_version}.so", "lib/libpython{python_version}.so.1.0"],
+ }}),
+)
+
+exports_files(["python", "{python_path}"])
+
+# Used to only download coverage toolchain when the coverage is collected by
+# bazel.
+config_setting(
+ name = "coverage_enabled",
+ values = {{"collect_code_coverage": "true"}},
+ visibility = ["//visibility:private"],
+)
+
+py_runtime(
+ name = "py3_runtime",
+ files = [":files"],
+{coverage_attr}
+ interpreter = "{python_path}",
+ python_version = "PY3",
+)
+
+py_runtime_pair(
+ name = "python_runtimes",
+ py2_runtime = None,
+ py3_runtime = ":py3_runtime",
+)
+
+py_cc_toolchain(
+ name = "py_cc_toolchain",
+ headers = ":python_headers",
+ python_version = "{python_version}",
+)
+""".format(
+ glob_exclude = repr(glob_exclude),
+ glob_include = repr(glob_include),
+ python_path = python_bin,
+ python_version = python_short_version,
+ python_version_nodot = python_short_version.replace(".", ""),
+ coverage_attr = coverage_attr_text,
+ )
+ rctx.delete("python")
+ rctx.symlink(python_bin, "python")
+ rctx.file(STANDALONE_INTERPRETER_FILENAME, "# File intentionally left blank. Indicates that this is an interpreter repo created by rules_python.")
+ rctx.file("BUILD.bazel", build_content)
+
+ attrs = {
+ "coverage_tool": rctx.attr.coverage_tool,
+ "distutils": rctx.attr.distutils,
+ "distutils_content": rctx.attr.distutils_content,
+ "ignore_root_user_error": rctx.attr.ignore_root_user_error,
+ "name": rctx.attr.name,
+ "patches": rctx.attr.patches,
+ "platform": platform,
+ "python_version": python_version,
+ "release_filename": release_filename,
+ "sha256": rctx.attr.sha256,
+ "strip_prefix": rctx.attr.strip_prefix,
+ }
+
+ if rctx.attr.url:
+ attrs["url"] = rctx.attr.url
+ else:
+ attrs["urls"] = urls
+
+ return attrs
+
+python_repository = repository_rule(
+ _python_repository_impl,
+ doc = "Fetches the external tools needed for the Python toolchain.",
+ attrs = {
+ "coverage_tool": attr.string(
+ # Mirrors the definition at
+ # https://github.com/bazelbuild/bazel/blob/master/src/main/starlark/builtins_bzl/common/python/py_runtime_rule.bzl
+ doc = """
+This is a target to use for collecting code coverage information from `py_binary`
+and `py_test` targets.
+
+If set, the target must either produce a single file or be an executable target.
+The path to the single file, or the executable if the target is executable,
+determines the entry point for the python coverage tool. The target and its
+runfiles will be added to the runfiles when coverage is enabled.
+
+The entry point for the tool must be loadable by a Python interpreter (e.g. a
+`.py` or `.pyc` file). It must accept the command line arguments
+of coverage.py (https://coverage.readthedocs.io), at least including
+the `run` and `lcov` subcommands.
+
+The target is accepted as a string by the python_repository and evaluated within
+the context of the toolchain repository.
+
+For more information see the official bazel docs
+(https://bazel.build/reference/be/python#py_runtime.coverage_tool).
+""",
+ ),
+ "distutils": attr.label(
+ allow_single_file = True,
+ doc = "A distutils.cfg file to be included in the Python installation. " +
+ "Either distutils or distutils_content can be specified, but not both.",
+ mandatory = False,
+ ),
+ "distutils_content": attr.string(
+ doc = "A distutils.cfg file content to be included in the Python installation. " +
+ "Either distutils or distutils_content can be specified, but not both.",
+ mandatory = False,
+ ),
+ "ignore_root_user_error": attr.bool(
+ default = False,
+ doc = "Whether the check for root should be ignored or not. This causes cache misses with .pyc files.",
+ mandatory = False,
+ ),
+ "patches": attr.label_list(
+ doc = "A list of patch files to apply to the unpacked interpreter",
+ mandatory = False,
+ ),
+ "platform": attr.string(
+ doc = "The platform name for the Python interpreter tarball.",
+ mandatory = True,
+ values = PLATFORMS.keys(),
+ ),
+ "python_version": attr.string(
+ doc = "The Python version.",
+ mandatory = True,
+ ),
+ "release_filename": attr.string(
+ doc = "The filename of the interpreter to be downloaded",
+ mandatory = True,
+ ),
+ "sha256": attr.string(
+ doc = "The SHA256 integrity hash for the Python interpreter tarball.",
+ mandatory = True,
+ ),
+ "strip_prefix": attr.string(
+ doc = "A directory prefix to strip from the extracted files.",
+ ),
+ "url": attr.string(
+ doc = "The URL of the interpreter to download. Exactly one of url and urls must be set.",
+ ),
+ "urls": attr.string_list(
+ doc = "The URL of the interpreter to download. Exactly one of url and urls must be set.",
+ ),
+ "zstd_sha256": attr.string(
+ default = "7c42d56fac126929a6a85dbc73ff1db2411d04f104fae9bdea51305663a83fd0",
+ ),
+ "zstd_url": attr.string(
+ default = "https://github.com/facebook/zstd/releases/download/v{version}/zstd-{version}.tar.gz",
+ ),
+ "zstd_version": attr.string(
+ default = "1.5.2",
+ ),
+ },
+)
+
+# Wrapper macro around everything above, this is the primary API.
+def python_register_toolchains(
+ name,
+ python_version,
+ distutils = None,
+ distutils_content = None,
+ register_toolchains = True,
+ register_coverage_tool = False,
+ set_python_version_constraint = False,
+ tool_versions = TOOL_VERSIONS,
+ **kwargs):
+ """Convenience macro for users which does typical setup.
+
+ - Create a repository for each built-in platform like "python_linux_amd64" -
+ this repository is lazily fetched when Python is needed for that platform.
+ - Create a repository exposing toolchains for each platform like
+ "python_platforms".
+ - Register a toolchain pointing at each platform.
+ Users can avoid this macro and do these steps themselves, if they want more
+ control.
+ Args:
+ name: base name for all created repos, like "python38".
+ python_version: the Python version.
+ distutils: see the distutils attribute in the python_repository repository rule.
+ distutils_content: see the distutils_content attribute in the python_repository repository rule.
+ register_toolchains: Whether or not to register the downloaded toolchains.
+ register_coverage_tool: Whether or not to register the downloaded coverage tool to the toolchains.
+ NOTE: Coverage support using the toolchain is only supported in Bazel 6 and higher.
+
+ set_python_version_constraint: When set to true, target_compatible_with for the toolchains will include a version constraint.
+ tool_versions: a dict containing a mapping of version with SHASUM and platform info. If not supplied, the defaults
+ in python/versions.bzl will be used.
+ **kwargs: passed to each python_repositories call.
+ """
+
+ if BZLMOD_ENABLED:
+ # you cannot used native.register_toolchains when using bzlmod.
+ register_toolchains = False
+
+ base_url = kwargs.pop("base_url", DEFAULT_RELEASE_BASE_URL)
+
+ if python_version in MINOR_MAPPING:
+ python_version = MINOR_MAPPING[python_version]
+
+ toolchain_repo_name = "{name}_toolchains".format(name = name)
+
+ # When using unreleased Bazel versions, the version is an empty string
+ if native.bazel_version:
+ bazel_major = int(native.bazel_version.split(".")[0])
+ if bazel_major < 6:
+ if register_coverage_tool:
+ # buildifier: disable=print
+ print((
+ "WARNING: ignoring register_coverage_tool=True when " +
+ "registering @{name}: Bazel 6+ required, got {version}"
+ ).format(
+ name = name,
+ version = native.bazel_version,
+ ))
+ register_coverage_tool = False
+
+ for platform in PLATFORMS.keys():
+ sha256 = tool_versions[python_version]["sha256"].get(platform, None)
+ if not sha256:
+ continue
+
+ (release_filename, urls, strip_prefix, patches) = get_release_info(platform, python_version, base_url, tool_versions)
+
+ # allow passing in a tool version
+ coverage_tool = None
+ coverage_tool = tool_versions[python_version].get("coverage_tool", {}).get(platform, None)
+ if register_coverage_tool and coverage_tool == None:
+ coverage_tool = coverage_dep(
+ name = "{name}_{platform}_coverage".format(
+ name = name,
+ platform = platform,
+ ),
+ python_version = python_version,
+ platform = platform,
+ visibility = ["@{name}_{platform}//:__subpackages__".format(
+ name = name,
+ platform = platform,
+ )],
+ )
+
+ python_repository(
+ name = "{name}_{platform}".format(
+ name = name,
+ platform = platform,
+ ),
+ sha256 = sha256,
+ patches = patches,
+ platform = platform,
+ python_version = python_version,
+ release_filename = release_filename,
+ urls = urls,
+ distutils = distutils,
+ distutils_content = distutils_content,
+ strip_prefix = strip_prefix,
+ coverage_tool = coverage_tool,
+ **kwargs
+ )
+ if register_toolchains:
+ native.register_toolchains("@{toolchain_repo_name}//:{platform}_toolchain".format(
+ toolchain_repo_name = toolchain_repo_name,
+ platform = platform,
+ ))
+
+ toolchain_aliases(
+ name = name,
+ python_version = python_version,
+ user_repository_name = name,
+ )
+
+ # in bzlmod we write out our own toolchain repos
+ if BZLMOD_ENABLED:
+ return
+
+ toolchains_repo(
+ name = toolchain_repo_name,
+ python_version = python_version,
+ set_python_version_constraint = set_python_version_constraint,
+ user_repository_name = name,
+ )
+
+def python_register_multi_toolchains(
+ name,
+ python_versions,
+ default_version = None,
+ **kwargs):
+ """Convenience macro for registering multiple Python toolchains.
+
+ Args:
+ name: base name for each name in python_register_toolchains call.
+ python_versions: the Python version.
+ default_version: the default Python version. If not set, the first version in
+ python_versions is used.
+ **kwargs: passed to each python_register_toolchains call.
+ """
+ if len(python_versions) == 0:
+ fail("python_versions must not be empty")
+
+ if not default_version:
+ default_version = python_versions.pop(0)
+ for python_version in python_versions:
+ if python_version == default_version:
+ # We register the default version lastly so that it's not picked first when --platforms
+ # is set with a constraint during toolchain resolution. This is due to the fact that
+ # Bazel will match the unconstrained toolchain if we register it before the constrained
+ # ones.
+ continue
+ python_register_toolchains(
+ name = name + "_" + python_version.replace(".", "_"),
+ python_version = python_version,
+ set_python_version_constraint = True,
+ **kwargs
+ )
+ python_register_toolchains(
+ name = name + "_" + default_version.replace(".", "_"),
+ python_version = default_version,
+ set_python_version_constraint = False,
+ **kwargs
+ )
+
+ multi_toolchain_aliases(
+ name = name,
+ python_versions = {
+ python_version: name + "_" + python_version.replace(".", "_")
+ for python_version in (python_versions + [default_version])
+ },
+ )
diff --git a/python/runfiles/BUILD.bazel b/python/runfiles/BUILD.bazel
new file mode 100644
index 0000000..c6cfc2f
--- /dev/null
+++ b/python/runfiles/BUILD.bazel
@@ -0,0 +1,58 @@
+# Copyright 2019 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")
+load("//python:packaging.bzl", "py_wheel")
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+ visibility = ["//python:__pkg__"],
+)
+
+py_library(
+ name = "runfiles",
+ srcs = [
+ "__init__.py",
+ "runfiles.py",
+ ],
+ imports = [
+ # Add the repo root so `import python.runfiles.runfiles` works. This makes it agnostic
+ # to the --experimental_python_import_all_repositories setting.
+ "../..",
+ ],
+ visibility = ["//visibility:public"],
+)
+
+# This can be manually tested by running tests/runfiles/runfiles_wheel_integration_test.sh
+# We ought to have an automated integration test for it, too.
+# see https://github.com/bazelbuild/rules_python/issues/1002
+py_wheel(
+ name = "wheel",
+ # From https://pypi.org/classifiers/
+ classifiers = [
+ "Development Status :: 5 - Production/Stable",
+ "License :: OSI Approved :: Apache Software License",
+ ],
+ description_file = "README.rst",
+ dist_folder = "dist",
+ distribution = "bazel_runfiles",
+ homepage = "https://github.com/bazelbuild/rules_python",
+ strip_path_prefixes = ["python"],
+ twine = "@publish_deps_twine//:pkg",
+ # this can be replaced by building with --stamp --embed_label=1.2.3
+ version = "{BUILD_EMBED_LABEL}",
+ visibility = ["//visibility:public"],
+ deps = [":runfiles"],
+)
diff --git a/python/runfiles/README.rst b/python/runfiles/README.rst
new file mode 100644
index 0000000..ac61d2d
--- /dev/null
+++ b/python/runfiles/README.rst
@@ -0,0 +1,56 @@
+bazel-runfiles library
+======================
+
+This is a Bazel Runfiles lookup library for Bazel-built Python binaries and tests.
+
+Learn about runfiles: read `Runfiles guide <https://bazel.build/extending/rules#runfiles>`_
+or watch `Fabian's BazelCon talk <https://www.youtube.com/watch?v=5NbgUMH1OGo>`_.
+
+Typical Usage
+-------------
+
+1. Add the 'bazel-runfiles' dependency along with other third-party dependencies, for example in your
+ ``requirements.txt`` file.
+
+2. Depend on this runfiles library from your build rule, like you would other third-party libraries::
+
+ py_binary(
+ name = "my_binary",
+ ...
+ deps = [requirement("bazel-runfiles")],
+ )
+
+3. Import the runfiles library::
+
+ import runfiles # not "from runfiles import runfiles"
+
+4. Create a Runfiles object and use rlocation to look up runfile paths::
+
+ r = runfiles.Create()
+ ...
+ with open(r.Rlocation("my_workspace/path/to/my/data.txt"), "r") as f:
+ contents = f.readlines()
+ ...
+
+ The code above creates a manifest- or directory-based implementations based
+ on the environment variables in os.environ. See `Create()` for more info.
+
+ If you want to explicitly create a manifest- or directory-based
+ implementations, you can do so as follows::
+
+ r1 = runfiles.CreateManifestBased("path/to/foo.runfiles_manifest")
+
+ r2 = runfiles.CreateDirectoryBased("path/to/foo.runfiles/")
+
+ If you want to start subprocesses, and the subprocess can't automatically
+ find the correct runfiles directory, you can explicitly set the right
+ environment variables for them::
+
+ import subprocess
+ import runfiles
+
+ r = runfiles.Create()
+ env = {}
+ ...
+ env.update(r.EnvVars())
+ p = subprocess.Popen([r.Rlocation("path/to/binary")], env, ...) \ No newline at end of file
diff --git a/python/runfiles/__init__.py b/python/runfiles/__init__.py
new file mode 100644
index 0000000..3dc4141
--- /dev/null
+++ b/python/runfiles/__init__.py
@@ -0,0 +1,15 @@
+# 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.
+
+from .runfiles import *
diff --git a/python/runfiles/runfiles.py b/python/runfiles/runfiles.py
new file mode 100644
index 0000000..9bdb61b
--- /dev/null
+++ b/python/runfiles/runfiles.py
@@ -0,0 +1,368 @@
+# 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.
+
+"""Runfiles lookup library for Bazel-built Python binaries and tests.
+
+See README.md for usage instructions.
+"""
+import inspect
+import os
+import posixpath
+import sys
+
+if False:
+ # Mypy needs these symbols imported, but since they only exist in python 3.5+,
+ # this import may fail at runtime. Luckily mypy can follow this conditional import.
+ from typing import Callable, Dict, Optional, Tuple, Union
+
+
+def CreateManifestBased(manifest_path):
+ # type: (str) -> _Runfiles
+ return _Runfiles(_ManifestBased(manifest_path))
+
+
+def CreateDirectoryBased(runfiles_dir_path):
+ # type: (str) -> _Runfiles
+ return _Runfiles(_DirectoryBased(runfiles_dir_path))
+
+
+def Create(env=None):
+ # type: (Optional[Dict[str, str]]) -> Optional[_Runfiles]
+ """Returns a new `Runfiles` instance.
+
+ The returned object is either:
+ - manifest-based, meaning it looks up runfile paths from a manifest file, or
+ - directory-based, meaning it looks up runfile paths under a given directory
+ path
+
+ If `env` contains "RUNFILES_MANIFEST_FILE" with non-empty value, this method
+ returns a manifest-based implementation. The object eagerly reads and caches
+ the whole manifest file upon instantiation; this may be relevant for
+ performance consideration.
+
+ Otherwise, if `env` contains "RUNFILES_DIR" with non-empty value (checked in
+ this priority order), this method returns a directory-based implementation.
+
+ If neither cases apply, this method returns null.
+
+ Args:
+ env: {string: string}; optional; the map of environment variables. If None,
+ this function uses the environment variable map of this process.
+ Raises:
+ IOError: if some IO error occurs.
+ """
+ env_map = os.environ if env is None else env
+ manifest = env_map.get("RUNFILES_MANIFEST_FILE")
+ if manifest:
+ return CreateManifestBased(manifest)
+
+ directory = env_map.get("RUNFILES_DIR")
+ if directory:
+ return CreateDirectoryBased(directory)
+
+ return None
+
+
+class _Runfiles(object):
+ """Returns the runtime location of runfiles.
+
+ Runfiles are data-dependencies of Bazel-built binaries and tests.
+ """
+
+ def __init__(self, strategy):
+ # type: (Union[_ManifestBased, _DirectoryBased]) -> None
+ self._strategy = strategy
+ self._python_runfiles_root = _FindPythonRunfilesRoot()
+ self._repo_mapping = _ParseRepoMapping(
+ strategy.RlocationChecked("_repo_mapping")
+ )
+
+ def Rlocation(self, path, source_repo=None):
+ # type: (str, Optional[str]) -> Optional[str]
+ """Returns the runtime path of a runfile.
+
+ Runfiles are data-dependencies of Bazel-built binaries and tests.
+
+ The returned path may not be valid. The caller should check the path's
+ validity and that the path exists.
+
+ The function may return None. In that case the caller can be sure that the
+ rule does not know about this data-dependency.
+
+ Args:
+ path: string; runfiles-root-relative path of the runfile
+ source_repo: string; optional; the canonical name of the repository
+ whose repository mapping should be used to resolve apparent to
+ canonical repository names in `path`. If `None` (default), the
+ repository mapping of the repository containing the caller of this
+ method is used. Explicitly setting this parameter should only be
+ necessary for libraries that want to wrap the runfiles library. Use
+ `CurrentRepository` to obtain canonical repository names.
+ Returns:
+ the path to the runfile, which the caller should check for existence, or
+ None if the method doesn't know about this runfile
+ Raises:
+ TypeError: if `path` is not a string
+ ValueError: if `path` is None or empty, or it's absolute or not normalized
+ """
+ if not path:
+ raise ValueError()
+ if not isinstance(path, str):
+ raise TypeError()
+ if (
+ path.startswith("../")
+ or "/.." in path
+ or path.startswith("./")
+ or "/./" in path
+ or path.endswith("/.")
+ or "//" in path
+ ):
+ raise ValueError('path is not normalized: "%s"' % path)
+ if path[0] == "\\":
+ raise ValueError('path is absolute without a drive letter: "%s"' % path)
+ if os.path.isabs(path):
+ return path
+
+ if source_repo is None and self._repo_mapping:
+ # Look up runfiles using the repository mapping of the caller of the
+ # current method. If the repo mapping is empty, determining this
+ # name is not necessary.
+ source_repo = self.CurrentRepository(frame=2)
+
+ # Split off the first path component, which contains the repository
+ # name (apparent or canonical).
+ target_repo, _, remainder = path.partition("/")
+ if not remainder or (source_repo, target_repo) not in self._repo_mapping:
+ # One of the following is the case:
+ # - not using Bzlmod, so the repository mapping is empty and
+ # apparent and canonical repository names are the same
+ # - target_repo is already a canonical repository name and does not
+ # have to be mapped.
+ # - path did not contain a slash and referred to a root symlink,
+ # which also should not be mapped.
+ return self._strategy.RlocationChecked(path)
+
+ # target_repo is an apparent repository name. Look up the corresponding
+ # canonical repository name with respect to the current repository,
+ # identified by its canonical name.
+ target_canonical = self._repo_mapping[(source_repo, target_repo)]
+ return self._strategy.RlocationChecked(target_canonical + "/" + remainder)
+
+ def EnvVars(self):
+ # type: () -> Dict[str, str]
+ """Returns environment variables for subprocesses.
+
+ The caller should set the returned key-value pairs in the environment of
+ subprocesses in case those subprocesses are also Bazel-built binaries that
+ need to use runfiles.
+
+ Returns:
+ {string: string}; a dict; keys are environment variable names, values are
+ the values for these environment variables
+ """
+ return self._strategy.EnvVars()
+
+ def CurrentRepository(self, frame=1):
+ # type: (int) -> str
+ """Returns the canonical name of the caller's Bazel repository.
+
+ For example, this function returns '' (the empty string) when called
+ from the main repository and a string of the form
+ 'rules_python~0.13.0` when called from code in the repository
+ corresponding to the rules_python Bazel module.
+
+ More information about the difference between canonical repository
+ names and the `@repo` part of labels is available at:
+ https://bazel.build/build/bzlmod#repository-names
+
+ NOTE: This function inspects the callstack to determine where in the
+ runfiles the caller is located to determine which repository it came
+ from. This may fail or produce incorrect results depending on who the
+ caller is, for example if it is not represented by a Python source
+ file. Use the `frame` argument to control the stack lookup.
+
+ Args:
+ frame: int; the stack frame to return the repository name for.
+ Defaults to 1, the caller of the CurrentRepository function.
+
+ Returns:
+ The canonical name of the Bazel repository containing the file
+ containing the frame-th caller of this function
+
+ Raises:
+ ValueError: if the caller cannot be determined or the caller's file
+ path is not contained in the Python runfiles tree
+ """
+ # pylint:disable=protected-access # for sys._getframe
+ # pylint:disable=raise-missing-from # we're still supporting Python 2
+ try:
+ caller_path = inspect.getfile(sys._getframe(frame))
+ except (TypeError, ValueError):
+ raise ValueError("failed to determine caller's file path")
+ caller_runfiles_path = os.path.relpath(caller_path, self._python_runfiles_root)
+ if caller_runfiles_path.startswith(".." + os.path.sep):
+ raise ValueError(
+ "{} does not lie under the runfiles root {}".format(
+ caller_path, self._python_runfiles_root
+ )
+ )
+
+ caller_runfiles_directory = caller_runfiles_path[
+ : caller_runfiles_path.find(os.path.sep)
+ ]
+ # With Bzlmod, the runfiles directory of the main repository is always
+ # named "_main". Without Bzlmod, the value returned by this function is
+ # never used, so we just assume Bzlmod is enabled.
+ if caller_runfiles_directory == "_main":
+ # The canonical name of the main repository (also known as the
+ # workspace) is the empty string.
+ return ""
+ # For all other repositories, the name of the runfiles directory is the
+ # canonical name.
+ return caller_runfiles_directory
+
+
+def _FindPythonRunfilesRoot():
+ # type: () -> str
+ """Finds the root of the Python runfiles tree."""
+ root = __file__
+ # Walk up our own runfiles path to the root of the runfiles tree from which
+ # the current file is being run. This path coincides with what the Bazel
+ # Python stub sets up as sys.path[0]. Since that entry can be changed at
+ # runtime, we rederive it here.
+ for _ in range("rules_python/python/runfiles/runfiles.py".count("/") + 1):
+ root = os.path.dirname(root)
+ return root
+
+
+def _ParseRepoMapping(repo_mapping_path):
+ # type: (Optional[str]) -> Dict[Tuple[str, str], str]
+ """Parses the repository mapping manifest."""
+ # If the repository mapping file can't be found, that is not an error: We
+ # might be running without Bzlmod enabled or there may not be any runfiles.
+ # In this case, just apply an empty repo mapping.
+ if not repo_mapping_path:
+ return {}
+ try:
+ with open(repo_mapping_path, "r") as f:
+ content = f.read()
+ except FileNotFoundError:
+ return {}
+
+ repo_mapping = {}
+ for line in content.split("\n"):
+ if not line:
+ # Empty line following the last line break
+ break
+ current_canonical, target_local, target_canonical = line.split(",")
+ repo_mapping[(current_canonical, target_local)] = target_canonical
+
+ return repo_mapping
+
+
+class _ManifestBased(object):
+ """`Runfiles` strategy that parses a runfiles-manifest to look up runfiles."""
+
+ def __init__(self, path):
+ # type: (str) -> None
+ if not path:
+ raise ValueError()
+ if not isinstance(path, str):
+ raise TypeError()
+ self._path = path
+ self._runfiles = _ManifestBased._LoadRunfiles(path)
+
+ def RlocationChecked(self, path):
+ # type: (str) -> Optional[str]
+ """Returns the runtime path of a runfile."""
+ exact_match = self._runfiles.get(path)
+ if exact_match:
+ return exact_match
+ # If path references a runfile that lies under a directory that
+ # itself is a runfile, then only the directory is listed in the
+ # manifest. Look up all prefixes of path in the manifest and append
+ # the relative path from the prefix to the looked up path.
+ prefix_end = len(path)
+ while True:
+ prefix_end = path.rfind("/", 0, prefix_end - 1)
+ if prefix_end == -1:
+ return None
+ prefix_match = self._runfiles.get(path[0:prefix_end])
+ if prefix_match:
+ return prefix_match + "/" + path[prefix_end + 1 :]
+
+ @staticmethod
+ def _LoadRunfiles(path):
+ # type: (str) -> Dict[str, str]
+ """Loads the runfiles manifest."""
+ result = {}
+ with open(path, "r") as f:
+ for line in f:
+ line = line.strip()
+ if line:
+ tokens = line.split(" ", 1)
+ if len(tokens) == 1:
+ result[line] = line
+ else:
+ result[tokens[0]] = tokens[1]
+ return result
+
+ def _GetRunfilesDir(self):
+ # type: () -> str
+ if self._path.endswith("/MANIFEST") or self._path.endswith("\\MANIFEST"):
+ return self._path[: -len("/MANIFEST")]
+ elif self._path.endswith(".runfiles_manifest"):
+ return self._path[: -len("_manifest")]
+ else:
+ return ""
+
+ def EnvVars(self):
+ # type: () -> Dict[str, str]
+ directory = self._GetRunfilesDir()
+ return {
+ "RUNFILES_MANIFEST_FILE": self._path,
+ "RUNFILES_DIR": directory,
+ # TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
+ # pick up RUNFILES_DIR.
+ "JAVA_RUNFILES": directory,
+ }
+
+
+class _DirectoryBased(object):
+ """`Runfiles` strategy that appends runfiles paths to the runfiles root."""
+
+ def __init__(self, path):
+ # type: (str) -> None
+ if not path:
+ raise ValueError()
+ if not isinstance(path, str):
+ raise TypeError()
+ self._runfiles_root = path
+
+ def RlocationChecked(self, path):
+ # type: (str) -> str
+
+ # Use posixpath instead of os.path, because Bazel only creates a runfiles
+ # tree on Unix platforms, so `Create()` will only create a directory-based
+ # runfiles strategy on those platforms.
+ return posixpath.join(self._runfiles_root, path)
+
+ def EnvVars(self):
+ # type: () -> Dict[str, str]
+ return {
+ "RUNFILES_DIR": self._runfiles_root,
+ # TODO(laszlocsomor): remove JAVA_RUNFILES once the Java launcher can
+ # pick up RUNFILES_DIR.
+ "JAVA_RUNFILES": self._runfiles_root,
+ }
diff --git a/python/tests/toolchains/BUILD.bazel b/python/tests/toolchains/BUILD.bazel
new file mode 100644
index 0000000..2f804a4
--- /dev/null
+++ b/python/tests/toolchains/BUILD.bazel
@@ -0,0 +1,20 @@
+# Copyright 2022 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(":defs.bzl", "acceptance_tests")
+load(":versions_test.bzl", "versions_test_suite")
+
+versions_test_suite(name = "versions_test")
+
+acceptance_tests()
diff --git a/python/tests/toolchains/defs.bzl b/python/tests/toolchains/defs.bzl
new file mode 100644
index 0000000..653cde6
--- /dev/null
+++ b/python/tests/toolchains/defs.bzl
@@ -0,0 +1,176 @@
+# Copyright 2022 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.
+
+"""This module contains the definition for the toolchains testing rules.
+"""
+
+load("//python:versions.bzl", "PLATFORMS", "TOOL_VERSIONS")
+
+_WINDOWS_RUNNER_TEMPLATE = """\
+@ECHO OFF
+set PATHEXT=.COM;.EXE;.BAT
+powershell.exe -c "& ./{interpreter_path} {run_acceptance_test_py}"
+"""
+
+def _acceptance_test_impl(ctx):
+ workspace = ctx.actions.declare_file("/".join([ctx.attr.python_version, "WORKSPACE"]))
+ ctx.actions.expand_template(
+ template = ctx.file._workspace_tmpl,
+ output = workspace,
+ substitutions = {"%python_version%": ctx.attr.python_version},
+ )
+
+ build_bazel = ctx.actions.declare_file("/".join([ctx.attr.python_version, "BUILD.bazel"]))
+ ctx.actions.expand_template(
+ template = ctx.file._build_bazel_tmpl,
+ output = build_bazel,
+ substitutions = {"%python_version%": ctx.attr.python_version},
+ )
+
+ python_version_test = ctx.actions.declare_file("/".join([ctx.attr.python_version, "python_version_test.py"]))
+ ctx.actions.symlink(
+ target_file = ctx.file._python_version_test,
+ output = python_version_test,
+ )
+
+ run_acceptance_test_py = ctx.actions.declare_file("/".join([ctx.attr.python_version, "run_acceptance_test.py"]))
+ ctx.actions.expand_template(
+ template = ctx.file._run_acceptance_test_tmpl,
+ output = run_acceptance_test_py,
+ substitutions = {
+ "%is_windows%": str(ctx.attr.is_windows),
+ "%python_version%": ctx.attr.python_version,
+ "%test_location%": "/".join([ctx.attr.test_location, ctx.attr.python_version]),
+ },
+ )
+
+ toolchain = ctx.toolchains["@bazel_tools//tools/python:toolchain_type"]
+ py3_runtime = toolchain.py3_runtime
+ interpreter_path = py3_runtime.interpreter_path
+ if not interpreter_path:
+ interpreter_path = py3_runtime.interpreter.short_path
+
+ if ctx.attr.is_windows:
+ executable = ctx.actions.declare_file("run_test_{}.bat".format(ctx.attr.python_version))
+ ctx.actions.write(
+ output = executable,
+ content = _WINDOWS_RUNNER_TEMPLATE.format(
+ interpreter_path = interpreter_path.replace("../", "external/"),
+ run_acceptance_test_py = run_acceptance_test_py.short_path,
+ ),
+ is_executable = True,
+ )
+ else:
+ executable = ctx.actions.declare_file("run_test_{}.sh".format(ctx.attr.python_version))
+ ctx.actions.write(
+ output = executable,
+ content = "exec '{interpreter_path}' '{run_acceptance_test_py}'".format(
+ interpreter_path = interpreter_path,
+ run_acceptance_test_py = run_acceptance_test_py.short_path,
+ ),
+ is_executable = True,
+ )
+
+ files = [
+ build_bazel,
+ executable,
+ python_version_test,
+ run_acceptance_test_py,
+ workspace,
+ ] + ctx.files._distribution
+ return [DefaultInfo(
+ executable = executable,
+ files = depset(
+ direct = files,
+ transitive = [py3_runtime.files],
+ ),
+ runfiles = ctx.runfiles(
+ files = files,
+ transitive_files = py3_runtime.files,
+ ),
+ )]
+
+_acceptance_test = rule(
+ implementation = _acceptance_test_impl,
+ doc = "A rule for the toolchain acceptance tests.",
+ attrs = {
+ "is_windows": attr.bool(
+ doc = "(Provided by the macro) Whether this is running under Windows or not.",
+ mandatory = True,
+ ),
+ "python_version": attr.string(
+ doc = "The Python version to be used when requesting the toolchain.",
+ mandatory = True,
+ ),
+ "test_location": attr.string(
+ doc = "(Provided by the macro) The value of native.package_name().",
+ mandatory = True,
+ ),
+ "_build_bazel_tmpl": attr.label(
+ doc = "The BUILD.bazel template.",
+ allow_single_file = True,
+ default = Label("//python/tests/toolchains/workspace_template:BUILD.bazel.tmpl"),
+ ),
+ "_distribution": attr.label(
+ doc = "The rules_python source distribution.",
+ default = Label("//:distribution"),
+ ),
+ "_python_version_test": attr.label(
+ doc = "The python_version_test.py used to test the Python version.",
+ allow_single_file = True,
+ default = Label("//python/tests/toolchains/workspace_template:python_version_test.py"),
+ ),
+ "_run_acceptance_test_tmpl": attr.label(
+ doc = "The run_acceptance_test.py template.",
+ allow_single_file = True,
+ default = Label("//python/tests/toolchains:run_acceptance_test.py.tmpl"),
+ ),
+ "_workspace_tmpl": attr.label(
+ doc = "The WORKSPACE template.",
+ allow_single_file = True,
+ default = Label("//python/tests/toolchains/workspace_template:WORKSPACE.tmpl"),
+ ),
+ },
+ test = True,
+ toolchains = ["@bazel_tools//tools/python:toolchain_type"],
+)
+
+def acceptance_test(python_version, **kwargs):
+ _acceptance_test(
+ is_windows = select({
+ "@bazel_tools//src/conditions:host_windows": True,
+ "//conditions:default": False,
+ }),
+ python_version = python_version,
+ test_location = native.package_name(),
+ **kwargs
+ )
+
+# buildifier: disable=unnamed-macro
+def acceptance_tests():
+ """Creates a matrix of acceptance_test targets for all the toolchains.
+ """
+ for python_version in TOOL_VERSIONS.keys():
+ for platform, meta in PLATFORMS.items():
+ if platform not in TOOL_VERSIONS[python_version]["sha256"]:
+ continue
+ acceptance_test(
+ name = "python_{python_version}_{platform}_test".format(
+ python_version = python_version.replace(".", "_"),
+ platform = platform,
+ ),
+ python_version = python_version,
+ target_compatible_with = meta.compatible_with,
+ tags = ["acceptance-test"],
+ )
diff --git a/python/tests/toolchains/run_acceptance_test.py.tmpl b/python/tests/toolchains/run_acceptance_test.py.tmpl
new file mode 100644
index 0000000..150e1a9
--- /dev/null
+++ b/python/tests/toolchains/run_acceptance_test.py.tmpl
@@ -0,0 +1,70 @@
+# Copyright 2022 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 subprocess
+import unittest
+
+
+class TestPythonVersion(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ os.chdir("%test_location%")
+ rules_python_path = os.path.join(os.environ["TEST_SRCDIR"], "rules_python")
+
+ test_tmpdir = os.environ["TEST_TMPDIR"]
+ if %is_windows%:
+ home = os.path.join(test_tmpdir, "HOME")
+ os.mkdir(home)
+ os.environ["HOME"] = home
+
+ local_app_data = os.path.join(test_tmpdir, "LocalAppData")
+ os.mkdir(local_app_data)
+ os.environ["LocalAppData"] = local_app_data
+
+ # Bazelisk requires a cache directory be set
+ os.environ["XDG_CACHE_HOME"] = os.path.join(test_tmpdir, "xdg-cache-home")
+
+ # Unset this so this works when called by Bazel's latest Bazel build
+ # pipeline. It sets the following combination, which interfere with each other:
+ # * --sandbox_tmpfs_path=/tmp
+ # * --test_env=USE_BAZEL_VERSION
+ # * USE_BAZEL_VERSION=/tmp/<something>
+ os.environ.pop("USE_BAZEL_VERSION", None)
+
+ with open(".bazelrc", "w") as bazelrc:
+ bazelrc.write(
+ os.linesep.join(
+ [
+ 'build --override_repository rules_python="{}"'.format(
+ rules_python_path.replace("\\", "/")
+ ),
+ "build --test_output=errors",
+ ]
+ )
+ )
+
+ def test_match_toolchain(self):
+ output = subprocess.check_output(
+ f"bazel run @python//:python3 -- --version",
+ shell = True, # Shell needed to look up via PATH
+ text=True,
+ ).strip()
+ self.assertEqual(output, "Python %python_version%")
+
+ subprocess.run("bazel test //...", shell=True, check=True)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/python/tests/toolchains/versions_test.bzl b/python/tests/toolchains/versions_test.bzl
new file mode 100644
index 0000000..b885d22
--- /dev/null
+++ b/python/tests/toolchains/versions_test.bzl
@@ -0,0 +1,51 @@
+# Copyright 2022 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.
+
+"""Unit tests for starlark helpers
+See https://docs.bazel.build/versions/main/skylark/testing.html#for-testing-starlark-utilities
+"""
+
+load("@bazel_skylib//lib:unittest.bzl", "asserts", "unittest")
+load("//python:versions.bzl", "MINOR_MAPPING", "TOOL_VERSIONS")
+
+required_platforms = [
+ "x86_64-apple-darwin",
+ "x86_64-unknown-linux-gnu",
+]
+
+def _smoke_test_impl(ctx):
+ env = unittest.begin(ctx)
+ for version in TOOL_VERSIONS.keys():
+ platforms = TOOL_VERSIONS[version]["sha256"]
+ for required_platform in required_platforms:
+ asserts.true(
+ env,
+ required_platform in platforms.keys(),
+ "Missing platform {} for version {}".format(required_platform, version),
+ )
+ for minor in MINOR_MAPPING:
+ version = MINOR_MAPPING[minor]
+ asserts.true(
+ env,
+ version in TOOL_VERSIONS.keys(),
+ "Missing version {} in TOOL_VERSIONS".format(version),
+ )
+ return unittest.end(env)
+
+# The unittest library requires that we export the test cases as named test rules,
+# but their names are arbitrary and don't appear anywhere.
+_t0_test = unittest.make(_smoke_test_impl)
+
+def versions_test_suite(name):
+ unittest.suite(name, _t0_test)
diff --git a/python/tests/toolchains/workspace_template/BUILD.bazel b/python/tests/toolchains/workspace_template/BUILD.bazel
new file mode 100644
index 0000000..dd70844
--- /dev/null
+++ b/python/tests/toolchains/workspace_template/BUILD.bazel
@@ -0,0 +1,5 @@
+exports_files([
+ "BUILD.bazel.tmpl",
+ "WORKSPACE.tmpl",
+ "python_version_test.py",
+])
diff --git a/python/tests/toolchains/workspace_template/BUILD.bazel.tmpl b/python/tests/toolchains/workspace_template/BUILD.bazel.tmpl
new file mode 100644
index 0000000..4a45209
--- /dev/null
+++ b/python/tests/toolchains/workspace_template/BUILD.bazel.tmpl
@@ -0,0 +1,9 @@
+load("@rules_python//python:defs.bzl", "py_test")
+
+py_test(
+ name = "python_version_test",
+ srcs = ["python_version_test.py"],
+ env = {
+ "PYTHON_VERSION": "%python_version%",
+ },
+)
diff --git a/python/tests/toolchains/workspace_template/README.md b/python/tests/toolchains/workspace_template/README.md
new file mode 100644
index 0000000..b4d6e6a
--- /dev/null
+++ b/python/tests/toolchains/workspace_template/README.md
@@ -0,0 +1,4 @@
+# Toolchains testing WORKSPACE template
+
+This directory contains templates for generating acceptance tests for the
+toolchains.
diff --git a/python/tests/toolchains/workspace_template/WORKSPACE.tmpl b/python/tests/toolchains/workspace_template/WORKSPACE.tmpl
new file mode 100644
index 0000000..973e020
--- /dev/null
+++ b/python/tests/toolchains/workspace_template/WORKSPACE.tmpl
@@ -0,0 +1,39 @@
+# Copyright 2022 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.
+
+workspace(name = "workspace_test")
+
+local_repository(
+ name = "rules_python",
+ path = "",
+)
+
+load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+
+python_register_toolchains(
+ name = "python",
+ python_version = "%python_version%",
+)
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+http_archive(
+ name = "bazel_skylib",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ ],
+ sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d",
+)
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+bazel_skylib_workspace()
diff --git a/python/tests/toolchains/workspace_template/python_version_test.py b/python/tests/toolchains/workspace_template/python_version_test.py
new file mode 100644
index 0000000..c82611c
--- /dev/null
+++ b/python/tests/toolchains/workspace_template/python_version_test.py
@@ -0,0 +1,26 @@
+# Copyright 2022 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 unittest
+
+
+class TestPythonVersion(unittest.TestCase):
+ def test_match_toolchain(self):
+ self.assertEqual(platform.python_version(), os.getenv("PYTHON_VERSION"))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/python/versions.bzl b/python/versions.bzl
new file mode 100644
index 0000000..a88c982
--- /dev/null
+++ b/python/versions.bzl
@@ -0,0 +1,406 @@
+# Copyright 2022 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 Python versions we use for the toolchains.
+"""
+
+# Values returned by https://bazel.build/rules/lib/repository_os.
+MACOS_NAME = "mac os"
+LINUX_NAME = "linux"
+WINDOWS_NAME = "windows"
+
+DEFAULT_RELEASE_BASE_URL = "https://github.com/indygreg/python-build-standalone/releases/download"
+
+# When updating the versions and releases, run the following command to get
+# the hashes:
+# bazel run //python/private:print_toolchains_checksums
+#
+# Note, to users looking at how to specify their tool versions, coverage_tool version for each
+# interpreter can be specified by:
+# "3.8.10": {
+# "url": "20210506/cpython-{python_version}-{platform}-pgo+lto-20210506T0943.tar.zst",
+# "sha256": {
+# "x86_64-apple-darwin": "8d06bec08db8cdd0f64f4f05ee892cf2fcbc58cfb1dd69da2caab78fac420238",
+# "x86_64-unknown-linux-gnu": "aec8c4c53373b90be7e2131093caa26063be6d9d826f599c935c0e1042af3355",
+# },
+# "coverage_tool": {
+# "x86_64-apple-darwin": "<label_for_darwin>"",
+# "x86_64-unknown-linux-gnu": "<label_for_linux>"",
+# },
+# "strip_prefix": "python",
+# },
+#
+# It is possible to provide lists in "url".
+#
+# buildifier: disable=unsorted-dict-items
+TOOL_VERSIONS = {
+ "3.8.10": {
+ "url": "20210506/cpython-{python_version}-{platform}-pgo+lto-20210506T0943.tar.zst",
+ "sha256": {
+ "x86_64-apple-darwin": "8d06bec08db8cdd0f64f4f05ee892cf2fcbc58cfb1dd69da2caab78fac420238",
+ "x86_64-unknown-linux-gnu": "aec8c4c53373b90be7e2131093caa26063be6d9d826f599c935c0e1042af3355",
+ },
+ "strip_prefix": "python",
+ },
+ "3.8.12": {
+ "url": "20220227/cpython-{python_version}+20220227-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "f9a3cbb81e0463d6615125964762d133387d561b226a30199f5b039b20f1d944",
+ # no aarch64-unknown-linux-gnu build available for 3.8.12
+ "x86_64-apple-darwin": "f323fbc558035c13a85ce2267d0fad9e89282268ecb810e364fff1d0a079d525",
+ "x86_64-pc-windows-msvc": "4658e08a00d60b1e01559b74d58ff4dd04da6df935d55f6268a15d6d0a679d74",
+ "x86_64-unknown-linux-gnu": "5be9c6d61e238b90dfd94755051c0d3a2d8023ebffdb4b0fa4e8fedd09a6cab6",
+ },
+ "strip_prefix": "python",
+ },
+ "3.8.13": {
+ "url": "20220802/cpython-{python_version}+20220802-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "ae4131253d890b013171cb5f7b03cadc585ae263719506f7b7e063a7cf6fde76",
+ # no aarch64-unknown-linux-gnu build available for 3.8.13
+ "x86_64-apple-darwin": "cd6e7c0a27daf7df00f6882eaba01490dd963f698e99aeee9706877333e0df69",
+ "x86_64-pc-windows-msvc": "f20643f1b3e263a56287319aea5c3888530c09ad9de3a5629b1a5d207807e6b9",
+ "x86_64-unknown-linux-gnu": "fb566629ccb5f76ef56d275a3f8017d683f1c20c5beb5d5f38b155ed11e16187",
+ },
+ "strip_prefix": "python",
+ },
+ "3.8.15": {
+ "url": "20221106/cpython-{python_version}+20221106-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "1e0a92d1a4f5e6d4a99f86b1cbf9773d703fe7fd032590f3e9c285c7a5eeb00a",
+ "aarch64-unknown-linux-gnu": "886ab33ced13c84bf59ce8ff79eba6448365bfcafea1bf415bd1d75e21b690aa",
+ "x86_64-apple-darwin": "70b57f28c2b5e1e3dd89f0d30edd5bc414e8b20195766cf328e1b26bed7890e1",
+ "x86_64-pc-windows-msvc": "2fdc3fa1c95f982179bbbaedae2b328197658638799b6dcb63f9f494b0de59e2",
+ "x86_64-unknown-linux-gnu": "e47edfb2ceaf43fc699e20c179ec428b6f3e497cf8e2dcd8e9c936d4b96b1e56",
+ },
+ "strip_prefix": "python",
+ },
+ "3.8.16": {
+ "url": "20230116/cpython-{python_version}+20230116-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "d1f408569d8807c1053939d7822b082a17545e363697e1ce3cfb1ee75834c7be",
+ "aarch64-unknown-linux-gnu": "15d00bc8400ed6d94c665a797dc8ed7a491ae25c5022e738dcd665cd29beec42",
+ "x86_64-apple-darwin": "484ba901f64fc7888bec5994eb49343dc3f9d00ed43df17ee9c40935aad4aa18",
+ "x86_64-pc-windows-msvc": "b446bec833eaba1bac9063bb9b4aeadfdf67fa81783b4487a90c56d408fb7994",
+ "x86_64-unknown-linux-gnu": "c890de112f1ae31283a31fefd2061d5c97bdd4d1bdd795552c7abddef2697ea1",
+ },
+ "strip_prefix": "python",
+ },
+ "3.9.10": {
+ "url": "20220227/cpython-{python_version}+20220227-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "ad66c2a3e7263147e046a32694de7b897a46fb0124409d29d3a93ede631c8aee",
+ "aarch64-unknown-linux-gnu": "12dd1f125762f47975990ec744532a1cf3db74ad60f4dfb476ca42deb7f78ca4",
+ "x86_64-apple-darwin": "fdaf594142446029e314a9beb91f1ac75af866320b50b8b968181e592550cd68",
+ "x86_64-pc-windows-msvc": "c145d9d8143ce163670af124b623d7a2405143a3708b033b4d33eed355e61b24",
+ "x86_64-unknown-linux-gnu": "455089cc576bd9a58db45e919d1fc867ecdbb0208067dffc845cc9bbf0701b70",
+ },
+ "strip_prefix": "python",
+ },
+ "3.9.12": {
+ "url": "20220502/cpython-{python_version}+20220502-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "8dee06c07cc6429df34b6abe091a4684a86f7cec76f5d1ccc1c3ce2bd11168df",
+ "aarch64-unknown-linux-gnu": "2ee1426c181e65133e57dc55c6a685cb1fb5e63ef02d684b8a667d5c031c4203",
+ "x86_64-apple-darwin": "2453ba7f76b3df3310353b48c881d6cff622ba06e30d2b6ae91588b2bc9e481a",
+ "x86_64-pc-windows-msvc": "3024147fd987d9e1b064a3d94932178ff8e0fe98cfea955704213c0762fee8df",
+ "x86_64-unknown-linux-gnu": "ccca12f698b3b810d79c52f007078f520d588232a36bc12ede944ec3ea417816",
+ },
+ "strip_prefix": "python",
+ },
+ "3.9.13": {
+ "url": "20220802/cpython-{python_version}+20220802-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "d9603edc296a2dcbc59d7ada780fd12527f05c3e0b99f7545112daf11636d6e5",
+ "aarch64-unknown-linux-gnu": "80415aac1b96255b9211f6a4c300f31e9940c7e07a23d0dec12b53aa52c0d25e",
+ "x86_64-apple-darwin": "9540a7efb7c8a54a48aff1cb9480e49588d9c0a3f934ad53f5b167338174afa3",
+ "x86_64-pc-windows-msvc": "b538127025a467c64b3351babca2e4d2ea7bdfb7867d5febb3529c34456cdcd4",
+ "x86_64-unknown-linux-gnu": "ce1cfca2715e7e646dd618a8cb9baff93000e345ccc979b801fc6ccde7ce97df",
+ },
+ "strip_prefix": "python",
+ },
+ "3.9.15": {
+ "url": "20221106/cpython-{python_version}+20221106-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "64dc7e1013481c9864152c3dd806c41144c79d5e9cd3140e185c6a5060bdc9ab",
+ "aarch64-unknown-linux-gnu": "52a8c0a67fb919f80962d992da1bddb511cdf92faf382701ce7673e10a8ff98f",
+ "x86_64-apple-darwin": "f2bcade6fc976c472f18f2b3204d67202d43ae55cf6f9e670f95e488f780da08",
+ "x86_64-pc-windows-msvc": "022daacab215679b87f0d200d08b9068a721605fa4721ebeda38220fc641ccf6",
+ "x86_64-unknown-linux-gnu": "cdc3a4cfddcd63b6cebdd75b14970e02d8ef0ac5be4d350e57ab5df56c19e85e",
+ },
+ "strip_prefix": "python",
+ },
+ "3.9.16": {
+ "url": "20230507/cpython-{python_version}+20230507-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "c1de1d854717a6245f45262ef1bb17b09e2c587590e7e3f406593c143ff875bd",
+ "aarch64-unknown-linux-gnu": "f629b75ebfcafe9ceee2e796b7e4df5cf8dbd14f3c021afca078d159ab797acf",
+ "ppc64le-unknown-linux-gnu": "ff3ac35c58f67839aff9b5185a976abd3d1abbe61af02089f7105e876c1fe284",
+ "x86_64-apple-darwin": "3abc4d5fbbc80f5f848f280927ac5d13de8dc03aabb6ae65d8247cbb68e6f6bf",
+ "x86_64-pc-windows-msvc": "cdabb47204e96ce7ea31fbd0b5ed586114dd7d8f8eddf60a509a7f70b48a1c5e",
+ "x86_64-unknown-linux-gnu": "2b6e146234a4ef2a8946081fc3fbfffe0765b80b690425a49ebe40b47c33445b",
+ },
+ "strip_prefix": "python",
+ },
+ "3.10.2": {
+ "url": "20220227/cpython-{python_version}+20220227-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "1409acd9a506e2d1d3b65c1488db4e40d8f19d09a7df099667c87a506f71c0ef",
+ "aarch64-unknown-linux-gnu": "8f351a8cc348bb45c0f95b8634c8345ec6e749e483384188ad865b7428342703",
+ "x86_64-apple-darwin": "8146ad4390710ec69b316a5649912df0247d35f4a42e2aa9615bffd87b3e235a",
+ "x86_64-pc-windows-msvc": "a1d9a594cd3103baa24937ad9150c1a389544b4350e859200b3e5c036ac352bd",
+ "x86_64-unknown-linux-gnu": "9b64eca2a94f7aff9409ad70bdaa7fbbf8148692662e764401883957943620dd",
+ },
+ "strip_prefix": "python",
+ },
+ "3.10.4": {
+ "url": "20220502/cpython-{python_version}+20220502-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "2c99983d1e83e4b6e7411ed9334019f193fba626344a50c36fba6c25d4de78a2",
+ "aarch64-unknown-linux-gnu": "d8098c0c54546637e7516f93b13403b11f9db285def8d7abd825c31407a13d7e",
+ "x86_64-apple-darwin": "f2711eaffff3477826a401d09a013c6802f11c04c63ab3686aa72664f1216a05",
+ "x86_64-pc-windows-msvc": "bee24a3a5c83325215521d261d73a5207ab7060ef3481f76f69b4366744eb81d",
+ "x86_64-unknown-linux-gnu": "f6f871e53a7b1469c13f9bd7920ad98c4589e549acad8e5a1e14760fff3dd5c9",
+ },
+ "strip_prefix": "python",
+ },
+ "3.10.6": {
+ "url": "20220802/cpython-{python_version}+20220802-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "efaf66acdb9a4eb33d57702607d2e667b1a319d58c167a43c96896b97419b8b7",
+ "aarch64-unknown-linux-gnu": "81625f5c97f61e2e3d7e9f62c484b1aa5311f21bd6545451714b949a29da5435",
+ "x86_64-apple-darwin": "7718411adf3ea1480f3f018a643eb0550282aefe39e5ecb3f363a4a566a9398c",
+ "x86_64-pc-windows-msvc": "91889a7dbdceea585ff4d3b7856a6bb8f8a4eca83a0ff52a73542c2e67220eaa",
+ "x86_64-unknown-linux-gnu": "55aa2190d28dcfdf414d96dc5dcea9fe048fadcd583dc3981fec020869826111",
+ },
+ "strip_prefix": "python",
+ },
+ "3.10.8": {
+ "url": "20221106/cpython-{python_version}+20221106-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "d52b03817bd245d28e0a8b2f715716cd0fcd112820ccff745636932c76afa20a",
+ "aarch64-unknown-linux-gnu": "33170bef18c811906b738be530f934640491b065bf16c4d276c6515321918132",
+ "x86_64-apple-darwin": "525b79c7ce5de90ab66bd07b0ac1008bafa147ddc8a41bef15ffb7c9c1e9e7c5",
+ "x86_64-pc-windows-msvc": "f2b6d2f77118f06dd2ca04dae1175e44aaa5077a5ed8ddc63333c15347182bfe",
+ "x86_64-unknown-linux-gnu": "6c8db44ae0e18e320320bbaaafd2d69cde8bfea171ae2d651b7993d1396260b7",
+ },
+ "strip_prefix": "python",
+ },
+ "3.10.9": {
+ "url": "20230116/cpython-{python_version}+20230116-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "018d05a779b2de7a476f3b3ff2d10f503d69d14efcedd0774e6dab8c22ef84ff",
+ "aarch64-unknown-linux-gnu": "2003750f40cd09d4bf7a850342613992f8d9454f03b3c067989911fb37e7a4d1",
+ "x86_64-apple-darwin": "0e685f98dce0e5bc8da93c7081f4e6c10219792e223e4b5886730fd73a7ba4c6",
+ "x86_64-pc-windows-msvc": "59c6970cecb357dc1d8554bd0540eb81ee7f6d16a07acf3d14ed294ece02c035",
+ "x86_64-unknown-linux-gnu": "d196347aeb701a53fe2bb2b095abec38d27d0fa0443f8a1c2023a1bed6e18cdf",
+ },
+ "strip_prefix": "python",
+ },
+ "3.10.11": {
+ "url": "20230507/cpython-{python_version}+20230507-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "8348bc3c2311f94ec63751fb71bd0108174be1c4def002773cf519ee1506f96f",
+ "aarch64-unknown-linux-gnu": "c7573fdb00239f86b22ea0e8e926ca881d24fde5e5890851339911d76110bc35",
+ "ppc64le-unknown-linux-gnu": "73a9d4c89ed51be39dd2de4e235078281087283e9fdedef65bec02f503e906ee",
+ "x86_64-apple-darwin": "bd3fc6e4da6f4033ebf19d66704e73b0804c22641ddae10bbe347c48f82374ad",
+ "x86_64-pc-windows-msvc": "9c2d3604a06fcd422289df73015cd00e7271d90de28d2c910f0e2309a7f73a68",
+ "x86_64-unknown-linux-gnu": "c5bcaac91bc80bfc29cf510669ecad12d506035ecb3ad85ef213416d54aecd79",
+ },
+ "strip_prefix": "python",
+ },
+ "3.11.1": {
+ "url": "20230116/cpython-{python_version}+20230116-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "4918cdf1cab742a90f85318f88b8122aeaa2d04705803c7b6e78e81a3dd40f80",
+ "aarch64-unknown-linux-gnu": "debf15783bdcb5530504f533d33fda75a7b905cec5361ae8f33da5ba6599f8b4",
+ "x86_64-apple-darwin": "20a4203d069dc9b710f70b09e7da2ce6f473d6b1110f9535fb6f4c469ed54733",
+ "x86_64-pc-windows-msvc": "edc08979cb0666a597466176511529c049a6f0bba8adf70df441708f766de5bf",
+ "x86_64-unknown-linux-gnu": "02a551fefab3750effd0e156c25446547c238688a32fabde2995c941c03a6423",
+ },
+ "strip_prefix": "python",
+ },
+ "3.11.3": {
+ "url": "20230507/cpython-{python_version}+20230507-{platform}-{build}.tar.gz",
+ "sha256": {
+ "aarch64-apple-darwin": "09e412506a8d63edbb6901742b54da9aa7faf120b8dbdce56c57b303fc892c86",
+ "aarch64-unknown-linux-gnu": "8190accbbbbcf7620f1ff6d668e4dd090c639665d11188ce864b62554d40e5ab",
+ "ppc64le-unknown-linux-gnu": "767d24f3570b35fedb945f5ac66224c8983f2d556ab83c5cfaa5f3666e9c212c",
+ "x86_64-apple-darwin": "f710b8d60621308149c100d5175fec39274ed0b9c99645484fd93d1716ef4310",
+ "x86_64-pc-windows-msvc": "24741066da6f35a7ff67bee65ce82eae870d84e1181843e64a7076d1571e95af",
+ "x86_64-unknown-linux-gnu": "da50b87d1ec42b3cb577dfd22a3655e43a53150f4f98a4bfb40757c9d7839ab5",
+ },
+ "strip_prefix": "python",
+ },
+}
+
+# buildifier: disable=unsorted-dict-items
+MINOR_MAPPING = {
+ "3.8": "3.8.15",
+ "3.9": "3.9.16",
+ "3.10": "3.10.9",
+ "3.11": "3.11.1",
+}
+
+PLATFORMS = {
+ "aarch64-apple-darwin": struct(
+ compatible_with = [
+ "@platforms//os:macos",
+ "@platforms//cpu:aarch64",
+ ],
+ os_name = MACOS_NAME,
+ # Matches the value returned from:
+ # repository_ctx.execute(["uname", "-m"]).stdout.strip()
+ arch = "arm64",
+ ),
+ "aarch64-unknown-linux-gnu": struct(
+ compatible_with = [
+ "@platforms//os:linux",
+ "@platforms//cpu:aarch64",
+ ],
+ os_name = LINUX_NAME,
+ # Note: this string differs between OSX and Linux
+ # Matches the value returned from:
+ # repository_ctx.execute(["uname", "-m"]).stdout.strip()
+ arch = "aarch64",
+ ),
+ "ppc64le-unknown-linux-gnu": struct(
+ compatible_with = [
+ "@platforms//os:linux",
+ "@platforms//cpu:ppc",
+ ],
+ os_name = LINUX_NAME,
+ # Note: this string differs between OSX and Linux
+ # Matches the value returned from:
+ # repository_ctx.execute(["uname", "-m"]).stdout.strip()
+ arch = "ppc64le",
+ ),
+ "x86_64-apple-darwin": struct(
+ compatible_with = [
+ "@platforms//os:macos",
+ "@platforms//cpu:x86_64",
+ ],
+ os_name = MACOS_NAME,
+ arch = "x86_64",
+ ),
+ "x86_64-pc-windows-msvc": struct(
+ compatible_with = [
+ "@platforms//os:windows",
+ "@platforms//cpu:x86_64",
+ ],
+ os_name = WINDOWS_NAME,
+ arch = "x86_64",
+ ),
+ "x86_64-unknown-linux-gnu": struct(
+ compatible_with = [
+ "@platforms//os:linux",
+ "@platforms//cpu:x86_64",
+ ],
+ os_name = LINUX_NAME,
+ arch = "x86_64",
+ ),
+}
+
+def get_release_info(platform, python_version, base_url = DEFAULT_RELEASE_BASE_URL, tool_versions = TOOL_VERSIONS):
+ """Resolve the release URL for the requested interpreter version
+
+ Args:
+ platform: The platform string for the interpreter
+ python_version: The version of the intterpreter to get
+ base_url: The URL to prepend to the 'url' attr in the tool_versions dict
+ tool_versions: A dict listing the interpreter versions, their SHAs and URL
+
+ Returns:
+ A tuple of (filename, url, and archive strip prefix)
+ """
+
+ url = tool_versions[python_version]["url"]
+
+ if type(url) == type({}):
+ url = url[platform]
+
+ if type(url) != type([]):
+ url = [url]
+
+ strip_prefix = tool_versions[python_version].get("strip_prefix", None)
+ if type(strip_prefix) == type({}):
+ strip_prefix = strip_prefix[platform]
+
+ release_filename = None
+ rendered_urls = []
+ for u in url:
+ release_filename = u.format(
+ platform = platform,
+ python_version = python_version,
+ build = "shared-install_only" if (WINDOWS_NAME in platform) else "install_only",
+ )
+ if "://" in release_filename: # is absolute url?
+ rendered_urls.append(release_filename)
+ else:
+ rendered_urls.append("/".join([base_url, release_filename]))
+
+ if release_filename == None:
+ fail("release_filename should be set by now; were any download URLs given?")
+
+ patches = tool_versions[python_version].get("patches", [])
+ if type(patches) == type({}):
+ if platform in patches.keys():
+ patches = patches[platform]
+ else:
+ patches = []
+
+ return (release_filename, rendered_urls, strip_prefix, patches)
+
+def print_toolchains_checksums(name):
+ native.genrule(
+ name = name,
+ srcs = [],
+ outs = ["print_toolchains_checksums.sh"],
+ cmd = """\
+cat > "$@" <<'EOF'
+#!/bin/bash
+
+set -o errexit -o nounset -o pipefail
+
+echo "Fetching hashes..."
+
+{commands}
+EOF
+ """.format(
+ commands = "\n".join([
+ _commands_for_version(python_version)
+ for python_version in TOOL_VERSIONS.keys()
+ ]),
+ ),
+ executable = True,
+ )
+
+def _commands_for_version(python_version):
+ return "\n".join([
+ "echo \"{python_version}: {platform}: $$(curl --location --fail {release_url_sha256} 2>/dev/null || curl --location --fail {release_url} 2>/dev/null | shasum -a 256 | awk '{{ print $$1 }}')\"".format(
+ python_version = python_version,
+ platform = platform,
+ release_url = release_url,
+ release_url_sha256 = release_url + ".sha256",
+ )
+ for platform in TOOL_VERSIONS[python_version]["sha256"].keys()
+ for release_url in get_release_info(platform, python_version)[1]
+ ])
+
+def gen_python_config_settings(name = ""):
+ for platform in PLATFORMS.keys():
+ native.config_setting(
+ name = "{name}{platform}".format(name = name, platform = platform),
+ constraint_values = PLATFORMS[platform].compatible_with,
+ )
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..ee8c906
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,5 @@
+{
+ "extends": [
+ "config:base"
+ ]
+}
diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel
new file mode 100644
index 0000000..2dd2282
--- /dev/null
+++ b/tests/BUILD.bazel
@@ -0,0 +1,34 @@
+load("@bazel_skylib//rules:build_test.bzl", "build_test")
+load("//tools/bazel_integration_test:bazel_integration_test.bzl", "bazel_integration_test")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+bazel_integration_test(
+ name = "pip_repository_entry_points_example",
+ timeout = "long",
+ # The dependencies needed for this test are not cross-platform: https://github.com/bazelbuild/rules_python/issues/260
+ tags = ["fix-windows"],
+)
+
+build_test(
+ name = "bzl_libraries_build_test",
+ targets = [
+ # keep sorted
+ "//python:current_py_toolchain_bzl",
+ "//python:defs_bzl",
+ "//python:proto_bzl",
+ "//python:py_binary_bzl",
+ "//python:py_cc_link_params_info_bzl",
+ "//python:py_import_bzl",
+ "//python:py_info_bzl",
+ "//python:py_library_bzl",
+ "//python:py_runtime_bzl",
+ "//python:py_runtime_info_bzl",
+ "//python:py_runtime_pair_bzl",
+ "//python:py_test_bzl",
+ "//python/cc:py_cc_toolchain_bzl",
+ "//python/cc:py_cc_toolchain_info_bzl",
+ ],
+)
diff --git a/tests/cc/BUILD.bazel b/tests/cc/BUILD.bazel
new file mode 100644
index 0000000..876d163
--- /dev/null
+++ b/tests/cc/BUILD.bazel
@@ -0,0 +1,106 @@
+# 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.
+
+load("@rules_cc//cc:defs.bzl", "cc_toolchain", "cc_toolchain_suite")
+load("@rules_testing//lib:util.bzl", "PREVENT_IMPLICIT_BUILDING_TAGS")
+load("//python/cc:py_cc_toolchain.bzl", "py_cc_toolchain")
+load(":fake_cc_toolchain_config.bzl", "fake_cc_toolchain_config")
+
+package(default_visibility = ["//:__subpackages__"])
+
+toolchain(
+ name = "fake_py_cc_toolchain",
+ tags = PREVENT_IMPLICIT_BUILDING_TAGS,
+ toolchain = ":fake_py_cc_toolchain_impl",
+ toolchain_type = "@rules_python//python/cc:toolchain_type",
+)
+
+py_cc_toolchain(
+ name = "fake_py_cc_toolchain_impl",
+ headers = ":fake_headers",
+ python_version = "3.999",
+ tags = PREVENT_IMPLICIT_BUILDING_TAGS,
+)
+
+# buildifier: disable=native-cc
+cc_library(
+ name = "fake_headers",
+ hdrs = ["fake_header.h"],
+ data = ["data.txt"],
+ includes = ["fake_include"],
+ tags = PREVENT_IMPLICIT_BUILDING_TAGS,
+)
+
+cc_toolchain_suite(
+ name = "cc_toolchain_suite",
+ tags = ["manual"],
+ toolchains = {
+ "darwin_x86_64": ":mac_toolchain",
+ "k8": ":linux_toolchain",
+ },
+)
+
+filegroup(name = "empty")
+
+cc_toolchain(
+ name = "mac_toolchain",
+ all_files = ":empty",
+ compiler_files = ":empty",
+ dwp_files = ":empty",
+ linker_files = ":empty",
+ objcopy_files = ":empty",
+ strip_files = ":empty",
+ supports_param_files = 0,
+ toolchain_config = ":mac_toolchain_config",
+ toolchain_identifier = "mac-toolchain",
+)
+
+toolchain(
+ name = "mac_toolchain_definition",
+ target_compatible_with = ["@platforms//os:macos"],
+ toolchain = ":mac_toolchain",
+ toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
+
+fake_cc_toolchain_config(
+ name = "mac_toolchain_config",
+ target_cpu = "darwin_x86_64",
+ toolchain_identifier = "mac-toolchain",
+)
+
+cc_toolchain(
+ name = "linux_toolchain",
+ all_files = ":empty",
+ compiler_files = ":empty",
+ dwp_files = ":empty",
+ linker_files = ":empty",
+ objcopy_files = ":empty",
+ strip_files = ":empty",
+ supports_param_files = 0,
+ toolchain_config = ":linux_toolchain_config",
+ toolchain_identifier = "linux-toolchain",
+)
+
+toolchain(
+ name = "linux_toolchain_definition",
+ target_compatible_with = ["@platforms//os:linux"],
+ toolchain = ":linux_toolchain",
+ toolchain_type = "@bazel_tools//tools/cpp:toolchain_type",
+)
+
+fake_cc_toolchain_config(
+ name = "linux_toolchain_config",
+ target_cpu = "k8",
+ toolchain_identifier = "linux-toolchain",
+)
diff --git a/tests/cc/current_py_cc_headers/BUILD.bazel b/tests/cc/current_py_cc_headers/BUILD.bazel
new file mode 100644
index 0000000..e2d6a1b
--- /dev/null
+++ b/tests/cc/current_py_cc_headers/BUILD.bazel
@@ -0,0 +1,17 @@
+# 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.
+
+load(":current_py_cc_headers_tests.bzl", "current_py_cc_headers_test_suite")
+
+current_py_cc_headers_test_suite(name = "current_py_cc_headers_tests")
diff --git a/tests/cc/current_py_cc_headers/current_py_cc_headers_tests.bzl b/tests/cc/current_py_cc_headers/current_py_cc_headers_tests.bzl
new file mode 100644
index 0000000..2b8b2ee
--- /dev/null
+++ b/tests/cc/current_py_cc_headers/current_py_cc_headers_tests.bzl
@@ -0,0 +1,69 @@
+# 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.
+
+"""Tests for current_py_cc_headers."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
+load("@rules_testing//lib:truth.bzl", "matching")
+load("//tests:cc_info_subject.bzl", "cc_info_subject")
+
+_tests = []
+
+def _test_current_toolchain_headers(name):
+ analysis_test(
+ name = name,
+ impl = _test_current_toolchain_headers_impl,
+ target = "//python/cc:current_py_cc_headers",
+ config_settings = {
+ "//command_line_option:extra_toolchains": [str(Label("//tests/cc:all"))],
+ },
+ attrs = {
+ "header": attr.label(
+ default = "//tests/cc:fake_header.h",
+ allow_single_file = True,
+ ),
+ },
+ )
+
+def _test_current_toolchain_headers_impl(env, target):
+ # Check that the forwarded CcInfo looks vaguely correct.
+ compilation_context = env.expect.that_target(target).provider(
+ CcInfo,
+ factory = cc_info_subject,
+ ).compilation_context()
+ compilation_context.direct_headers().contains_exactly([
+ env.ctx.file.header,
+ ])
+ compilation_context.direct_public_headers().contains_exactly([
+ env.ctx.file.header,
+ ])
+
+ # NOTE: The include dir gets added twice, once for the source path,
+ # and once for the config-specific path.
+ compilation_context.system_includes().contains_at_least_predicates([
+ matching.str_matches("*/fake_include"),
+ ])
+
+ # Check that the forward DefaultInfo looks correct
+ env.expect.that_target(target).runfiles().contains_predicate(
+ matching.str_matches("*/cc/data.txt"),
+ )
+
+_tests.append(_test_current_toolchain_headers)
+
+def current_py_cc_headers_test_suite(name):
+ test_suite(
+ name = name,
+ tests = _tests,
+ )
diff --git a/tests/cc/fake_cc_toolchain_config.bzl b/tests/cc/fake_cc_toolchain_config.bzl
new file mode 100644
index 0000000..b3214a6
--- /dev/null
+++ b/tests/cc/fake_cc_toolchain_config.bzl
@@ -0,0 +1,37 @@
+# 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.
+
+"""Fake for providing CcToolchainConfigInfo."""
+
+def _impl(ctx):
+ return cc_common.create_cc_toolchain_config_info(
+ ctx = ctx,
+ toolchain_identifier = ctx.attr.toolchain_identifier,
+ host_system_name = "local",
+ target_system_name = "local",
+ target_cpu = ctx.attr.target_cpu,
+ target_libc = "unknown",
+ compiler = "clang",
+ abi_version = "unknown",
+ abi_libc_version = "unknown",
+ )
+
+fake_cc_toolchain_config = rule(
+ implementation = _impl,
+ attrs = {
+ "target_cpu": attr.string(),
+ "toolchain_identifier": attr.string(),
+ },
+ provides = [CcToolchainConfigInfo],
+)
diff --git a/tests/cc/py_cc_toolchain/BUILD.bazel b/tests/cc/py_cc_toolchain/BUILD.bazel
new file mode 100644
index 0000000..57d030c
--- /dev/null
+++ b/tests/cc/py_cc_toolchain/BUILD.bazel
@@ -0,0 +1,3 @@
+load(":py_cc_toolchain_tests.bzl", "py_cc_toolchain_test_suite")
+
+py_cc_toolchain_test_suite(name = "py_cc_toolchain_tests")
diff --git a/tests/cc/py_cc_toolchain/py_cc_toolchain_tests.bzl b/tests/cc/py_cc_toolchain/py_cc_toolchain_tests.bzl
new file mode 100644
index 0000000..609518d
--- /dev/null
+++ b/tests/cc/py_cc_toolchain/py_cc_toolchain_tests.bzl
@@ -0,0 +1,83 @@
+# 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.
+
+"""Tests for py_cc_toolchain."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
+load("@rules_testing//lib:truth.bzl", "matching")
+load("//tests:cc_info_subject.bzl", "cc_info_subject")
+load("//tests:default_info_subject.bzl", "default_info_subject")
+load("//tests:py_cc_toolchain_info_subject.bzl", "PyCcToolchainInfoSubject")
+
+_tests = []
+
+def _py_cc_toolchain_test(name):
+ analysis_test(
+ name = name,
+ impl = _py_cc_toolchain_test_impl,
+ target = "//tests/cc:fake_py_cc_toolchain_impl",
+ attrs = {
+ "header": attr.label(
+ default = "//tests/cc:fake_header.h",
+ allow_single_file = True,
+ ),
+ },
+ )
+
+def _py_cc_toolchain_test_impl(env, target):
+ env.expect.that_target(target).has_provider(platform_common.ToolchainInfo)
+
+ toolchain = PyCcToolchainInfoSubject.new(
+ target[platform_common.ToolchainInfo].py_cc_toolchain,
+ meta = env.expect.meta.derive(expr = "py_cc_toolchain_info"),
+ )
+ toolchain.python_version().equals("3.999")
+
+ headers_providers = toolchain.headers().providers_map()
+ headers_providers.keys().contains_exactly(["CcInfo", "DefaultInfo"])
+
+ cc_info = headers_providers.get("CcInfo", factory = cc_info_subject)
+
+ compilation_context = cc_info.compilation_context()
+ compilation_context.direct_headers().contains_exactly([
+ env.ctx.file.header,
+ ])
+ compilation_context.direct_public_headers().contains_exactly([
+ env.ctx.file.header,
+ ])
+
+ # NOTE: The include dir gets added twice, once for the source path,
+ # and once for the config-specific path, but we don't care about that.
+ compilation_context.system_includes().contains_at_least_predicates([
+ matching.str_matches("*/fake_include"),
+ ])
+
+ # TODO: Once subjects.default_info is available, do
+ # default_info = headers_providers.get("DefaultInfo", factory=subjects.default_info)
+ # https://github.com/bazelbuild/rules_python/issues/1297
+ default_info = default_info_subject(
+ headers_providers.get("DefaultInfo", factory = lambda v, meta: v),
+ meta = env.expect.meta.derive(expr = "default_info"),
+ )
+ default_info.runfiles().contains_predicate(
+ matching.str_matches("*/cc/data.txt"),
+ )
+
+_tests.append(_py_cc_toolchain_test)
+
+def py_cc_toolchain_test_suite(name):
+ test_suite(
+ name = name,
+ tests = _tests,
+ )
diff --git a/tests/cc_info_subject.bzl b/tests/cc_info_subject.bzl
new file mode 100644
index 0000000..31ac03a
--- /dev/null
+++ b/tests/cc_info_subject.bzl
@@ -0,0 +1,128 @@
+# 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.
+"""CcInfo testing subject."""
+
+load("@rules_testing//lib:truth.bzl", "subjects")
+
+def cc_info_subject(info, *, meta):
+ """Creates a new `CcInfoSubject` for a CcInfo provider instance.
+
+ Args:
+ info: The CcInfo object.
+ meta: ExpectMeta object.
+
+ Returns:
+ A `CcInfoSubject` struct.
+ """
+
+ # buildifier: disable=uninitialized
+ public = struct(
+ # go/keep-sorted start
+ compilation_context = lambda *a, **k: _cc_info_subject_compilation_context(self, *a, **k),
+ # go/keep-sorted end
+ )
+ self = struct(
+ actual = info,
+ meta = meta,
+ )
+ return public
+
+def _cc_info_subject_compilation_context(self):
+ """Returns the CcInfo.compilation_context as a subject.
+
+ Args:
+ self: implicitly added.
+
+ Returns:
+ [`CompilationContext`] instance.
+ """
+ return _compilation_context_subject_new(
+ self.actual.compilation_context,
+ meta = self.meta.derive("compilation_context()"),
+ )
+
+def _compilation_context_subject_new(info, *, meta):
+ """Creates a CompilationContextSubject.
+
+ Args:
+ info: ([`CompilationContext`]) object instance.
+ meta: rules_testing `ExpectMeta` instance.
+
+ Returns:
+ [`CompilationContextSubject`] object.
+ """
+
+ # buildifier: disable=uninitialized
+ public = struct(
+ # go/keep-sorted start
+ direct_headers = lambda *a, **k: _compilation_context_subject_direct_headers(self, *a, **k),
+ direct_public_headers = lambda *a, **k: _compilation_context_subject_direct_public_headers(self, *a, **k),
+ system_includes = lambda *a, **k: _compilation_context_subject_system_includes(self, *a, **k),
+ # go/keep-sorted end
+ )
+ self = struct(
+ actual = info,
+ meta = meta,
+ )
+ return public
+
+def _compilation_context_subject_direct_headers(self):
+ """Returns the direct headers as a subjecct.
+
+ Args:
+ self: implicitly added
+
+ Returns:
+ [`CollectionSubject`] of `File` objects of the direct headers.
+ """
+ return subjects.collection(
+ self.actual.direct_headers,
+ meta = self.meta.derive("direct_headers()"),
+ container_name = "direct_headers",
+ element_plural_name = "header files",
+ )
+
+def _compilation_context_subject_direct_public_headers(self):
+ """Returns the direct public headers as a subjecct.
+
+ Args:
+ self: implicitly added
+
+ Returns:
+ [`CollectionSubject`] of `File` objects of the direct headers.
+ """
+ return subjects.collection(
+ self.actual.direct_public_headers,
+ meta = self.meta.derive("direct_public_headers()"),
+ container_name = "direct_public_headers",
+ element_plural_name = "public header files",
+ )
+
+def _compilation_context_subject_system_includes(self):
+ """Returns the system include directories as a subject.
+
+ NOTE: The system includes are the `cc_library.includes` attribute.
+
+ Args:
+ self: implicitly added
+
+ Returns:
+ [`CollectionSubject`] of [`str`]
+ """
+ return subjects.collection(
+ self.actual.system_includes.to_list(),
+ meta = self.meta.derive("includes()"),
+ container_name = "includes",
+ element_plural_name = "include paths",
+ )
diff --git a/tests/compile_pip_requirements/.bazelrc b/tests/compile_pip_requirements/.bazelrc
new file mode 100644
index 0000000..f23315a
--- /dev/null
+++ b/tests/compile_pip_requirements/.bazelrc
@@ -0,0 +1,5 @@
+test --test_output=errors
+
+# Windows requires these for multi-python support:
+build --enable_runfiles
+startup --windows_enable_symlinks
diff --git a/tests/compile_pip_requirements/.gitignore b/tests/compile_pip_requirements/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/tests/compile_pip_requirements/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/tests/compile_pip_requirements/BUILD.bazel b/tests/compile_pip_requirements/BUILD.bazel
new file mode 100644
index 0000000..ad5ee1a
--- /dev/null
+++ b/tests/compile_pip_requirements/BUILD.bazel
@@ -0,0 +1,79 @@
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+
+genrule(
+ name = "generate_requirements_extra_in",
+ srcs = [],
+ outs = ["requirements_extra.in"],
+ cmd = "echo 'setuptools~=65.6.3' > $@",
+)
+
+genrule(
+ name = "generate_requirements_in",
+ srcs = [],
+ outs = ["requirements.in"],
+ cmd = """
+cat > $@ <<EOF
+-r requirements_extra.in
+pip~=22.3.1
+EOF
+""",
+)
+
+compile_pip_requirements(
+ name = "requirements",
+ data = [
+ "requirements.in",
+ "requirements_extra.in",
+ ],
+ extra_args = [
+ "--allow-unsafe",
+ "--resolver=backtracking",
+ ],
+ requirements_in = "requirements.txt",
+ requirements_txt = "requirements_lock.txt",
+)
+
+compile_pip_requirements(
+ name = "requirements_nohashes",
+ data = [
+ "requirements.in",
+ "requirements_extra.in",
+ ],
+ extra_args = [
+ "--allow-unsafe",
+ "--resolver=backtracking",
+ ],
+ generate_hashes = False,
+ requirements_in = "requirements.txt",
+ requirements_txt = "requirements_nohashes_lock.txt",
+)
+
+genrule(
+ name = "generate_os_specific_requirements_in",
+ srcs = [],
+ outs = ["requirements_os_specific.in"],
+ cmd = """
+cat > $@ <<EOF
+pip==22.3.0 ; sys_platform == "linux"
+pip==22.2.2 ; sys_platform == "darwin"
+pip==22.2.1 ; sys_platform == "win32"
+EOF
+""",
+)
+
+compile_pip_requirements(
+ name = "os_specific_requirements",
+ data = [
+ "requirements_extra.in",
+ "requirements_os_specific.in",
+ ],
+ extra_args = [
+ "--allow-unsafe",
+ "--resolver=backtracking",
+ ],
+ requirements_darwin = "requirements_lock_darwin.txt",
+ requirements_in = "requirements_os_specific.in",
+ requirements_linux = "requirements_lock_linux.txt",
+ requirements_txt = "requirements_lock.txt",
+ requirements_windows = "requirements_lock_windows.txt",
+)
diff --git a/tests/compile_pip_requirements/README.md b/tests/compile_pip_requirements/README.md
new file mode 100644
index 0000000..3777624
--- /dev/null
+++ b/tests/compile_pip_requirements/README.md
@@ -0,0 +1,3 @@
+# compile_pip_requirements
+
+This test ensures that generated requirements.in can be used by compile_pip_requirements.
diff --git a/tests/compile_pip_requirements/WORKSPACE b/tests/compile_pip_requirements/WORKSPACE
new file mode 100644
index 0000000..d3a419c
--- /dev/null
+++ b/tests/compile_pip_requirements/WORKSPACE
@@ -0,0 +1,17 @@
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
+
+load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies")
+
+pip_install_dependencies()
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
diff --git a/tests/compile_pip_requirements/requirements.txt b/tests/compile_pip_requirements/requirements.txt
new file mode 100644
index 0000000..4826399
--- /dev/null
+++ b/tests/compile_pip_requirements/requirements.txt
@@ -0,0 +1 @@
+-r requirements.in
diff --git a/tests/compile_pip_requirements/requirements_lock.txt b/tests/compile_pip_requirements/requirements_lock.txt
new file mode 100644
index 0000000..4ca4a11
--- /dev/null
+++ b/tests/compile_pip_requirements/requirements_lock.txt
@@ -0,0 +1,14 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+pip==22.3.1 \
+ --hash=sha256:65fd48317359f3af8e593943e6ae1506b66325085ea64b706a998c6e83eeaf38 \
+ --hash=sha256:908c78e6bc29b676ede1c4d57981d490cb892eb45cd8c214ab6298125119e077
+ # via -r requirements.in
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via -r requirements_extra.in
diff --git a/tests/compile_pip_requirements/requirements_lock_darwin.txt b/tests/compile_pip_requirements/requirements_lock_darwin.txt
new file mode 100644
index 0000000..7b580a2
--- /dev/null
+++ b/tests/compile_pip_requirements/requirements_lock_darwin.txt
@@ -0,0 +1,10 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:os_specific_requirements.update
+#
+pip==22.2.2 ; sys_platform == "darwin" \
+ --hash=sha256:3fd1929db052f056d7a998439176d3333fa1b3f6c1ad881de1885c0717608a4b \
+ --hash=sha256:b61a374b5bc40a6e982426aede40c9b5a08ff20e640f5b56977f4f91fed1e39a
+ # via -r requirements_os_specific.in
diff --git a/tests/compile_pip_requirements/requirements_lock_linux.txt b/tests/compile_pip_requirements/requirements_lock_linux.txt
new file mode 100644
index 0000000..54eca17
--- /dev/null
+++ b/tests/compile_pip_requirements/requirements_lock_linux.txt
@@ -0,0 +1,10 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:os_specific_requirements.update
+#
+pip==22.3 ; sys_platform == "linux" \
+ --hash=sha256:1daab4b8d3b97d1d763caeb01a4640a2250a0ea899e257b1e44b9eded91e15ab \
+ --hash=sha256:8182aec21dad6c0a49a2a3d121a87cd524b950e0b6092b181625f07ebdde7530
+ # via -r requirements_os_specific.in
diff --git a/tests/compile_pip_requirements/requirements_lock_windows.txt b/tests/compile_pip_requirements/requirements_lock_windows.txt
new file mode 100644
index 0000000..5803d8e
--- /dev/null
+++ b/tests/compile_pip_requirements/requirements_lock_windows.txt
@@ -0,0 +1,10 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:os_specific_requirements.update
+#
+pip==22.2.1 ; sys_platform == "win32" \
+ --hash=sha256:0bbbc87dfbe6eed217beff0021f8b7dea04c8f4a0baa9d31dc4cff281ffc5b2b \
+ --hash=sha256:50516e47a2b79e77446f0d05649f0d53772c192571486236b1905492bfc24bac
+ # via -r requirements_os_specific.in
diff --git a/tests/compile_pip_requirements/requirements_nohashes_lock.txt b/tests/compile_pip_requirements/requirements_nohashes_lock.txt
new file mode 100644
index 0000000..2b08a8e
--- /dev/null
+++ b/tests/compile_pip_requirements/requirements_nohashes_lock.txt
@@ -0,0 +1,10 @@
+#
+# This file is autogenerated by pip-compile with Python 3.9
+# by the following command:
+#
+# bazel run //:requirements_nohashes.update
+#
+pip==22.3.1
+ # via -r requirements.in
+setuptools==65.6.3
+ # via -r requirements_extra.in
diff --git a/tests/compile_pip_requirements_test_from_external_workspace/.bazelrc b/tests/compile_pip_requirements_test_from_external_workspace/.bazelrc
new file mode 100644
index 0000000..b98fc09
--- /dev/null
+++ b/tests/compile_pip_requirements_test_from_external_workspace/.bazelrc
@@ -0,0 +1 @@
+test --test_output=errors
diff --git a/tests/compile_pip_requirements_test_from_external_workspace/.gitignore b/tests/compile_pip_requirements_test_from_external_workspace/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/tests/compile_pip_requirements_test_from_external_workspace/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/tests/compile_pip_requirements_test_from_external_workspace/BUILD.bazel b/tests/compile_pip_requirements_test_from_external_workspace/BUILD.bazel
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tests/compile_pip_requirements_test_from_external_workspace/BUILD.bazel
diff --git a/tests/compile_pip_requirements_test_from_external_workspace/README.md b/tests/compile_pip_requirements_test_from_external_workspace/README.md
new file mode 100644
index 0000000..8ce77ca
--- /dev/null
+++ b/tests/compile_pip_requirements_test_from_external_workspace/README.md
@@ -0,0 +1,3 @@
+# compile_pip_requirements_test_from_external_workspace
+
+This test checks that compile_pip_requirements test can be run from external workspaces without invalidating the lock file.
diff --git a/tests/compile_pip_requirements_test_from_external_workspace/WORKSPACE b/tests/compile_pip_requirements_test_from_external_workspace/WORKSPACE
new file mode 100644
index 0000000..35686a1
--- /dev/null
+++ b/tests/compile_pip_requirements_test_from_external_workspace/WORKSPACE
@@ -0,0 +1,36 @@
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
+
+load("@rules_python//python/pip_install:repositories.bzl", "pip_install_dependencies")
+
+pip_install_dependencies()
+
+python_register_toolchains(
+ name = "python39",
+ python_version = "3.9",
+)
+
+load("@python39//:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "pip_parse")
+
+local_repository(
+ name = "external_repository",
+ path = "../compile_pip_requirements",
+)
+
+pip_parse(
+ name = "pypi",
+ python_interpreter_target = interpreter,
+ requirements_lock = "@external_repository//:requirements_lock.txt",
+)
+
+load("@pypi//:requirements.bzl", "install_deps")
+
+# Initialize repositories for all packages in requirements_lock.txt.
+install_deps()
diff --git a/tests/config_settings/transition/BUILD.bazel b/tests/config_settings/transition/BUILD.bazel
new file mode 100644
index 0000000..21fa50e
--- /dev/null
+++ b/tests/config_settings/transition/BUILD.bazel
@@ -0,0 +1,3 @@
+load(":py_args_tests.bzl", "py_args_test_suite")
+
+py_args_test_suite(name = "py_args_tests")
diff --git a/tests/config_settings/transition/py_args_tests.bzl b/tests/config_settings/transition/py_args_tests.bzl
new file mode 100644
index 0000000..4538c88
--- /dev/null
+++ b/tests/config_settings/transition/py_args_tests.bzl
@@ -0,0 +1,68 @@
+# 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.
+
+""
+
+load("@rules_testing//lib:test_suite.bzl", "test_suite")
+load("//python/config_settings/private:py_args.bzl", "py_args") # buildifier: disable=bzl-visibility
+
+_tests = []
+
+def _test_py_args_default(env):
+ actual = py_args("foo", {})
+
+ want = {
+ "args": None,
+ "data": None,
+ "deps": None,
+ "env": None,
+ "main": "foo.py",
+ "srcs": None,
+ }
+ env.expect.that_dict(actual).contains_exactly(want)
+
+_tests.append(_test_py_args_default)
+
+def _test_kwargs_get_consumed(env):
+ kwargs = {
+ "args": ["some", "args"],
+ "data": ["data"],
+ "deps": ["deps"],
+ "env": {"key": "value"},
+ "main": "__main__.py",
+ "srcs": ["__main__.py"],
+ "visibility": ["//visibility:public"],
+ }
+ actual = py_args("bar_bin", kwargs)
+
+ want = {
+ "args": ["some", "args"],
+ "data": ["data"],
+ "deps": ["deps"],
+ "env": {"key": "value"},
+ "main": "__main__.py",
+ "srcs": ["__main__.py"],
+ }
+ env.expect.that_dict(actual).contains_exactly(want)
+ env.expect.that_dict(kwargs).keys().contains_exactly(["visibility"])
+
+_tests.append(_test_kwargs_get_consumed)
+
+def py_args_test_suite(name):
+ """Create the test suite.
+
+ Args:
+ name: the name of the test suite
+ """
+ test_suite(name = name, basic_tests = _tests)
diff --git a/tests/default_info_subject.bzl b/tests/default_info_subject.bzl
new file mode 100644
index 0000000..205dc1e
--- /dev/null
+++ b/tests/default_info_subject.bzl
@@ -0,0 +1,34 @@
+# 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.
+"""DefaultInfo testing subject."""
+
+# TODO: Load this through truth.bzl#subjects when made available
+# https://github.com/bazelbuild/rules_testing/issues/54
+load("@rules_testing//lib/private:runfiles_subject.bzl", "RunfilesSubject") # buildifier: disable=bzl-visibility
+
+# TODO: Use rules_testing's DefaultInfoSubject once it's available
+# https://github.com/bazelbuild/rules_testing/issues/52
+def default_info_subject(info, *, meta):
+ # buildifier: disable=uninitialized
+ public = struct(
+ runfiles = lambda *a, **k: _default_info_subject_runfiles(self, *a, **k),
+ )
+ self = struct(actual = info, meta = meta)
+ return public
+
+def _default_info_subject_runfiles(self):
+ return RunfilesSubject.new(
+ self.actual.default_runfiles,
+ meta = self.meta.derive("runfiles()"),
+ )
diff --git a/tests/ignore_root_user_error/.bazelrc b/tests/ignore_root_user_error/.bazelrc
new file mode 100644
index 0000000..f23315a
--- /dev/null
+++ b/tests/ignore_root_user_error/.bazelrc
@@ -0,0 +1,5 @@
+test --test_output=errors
+
+# Windows requires these for multi-python support:
+build --enable_runfiles
+startup --windows_enable_symlinks
diff --git a/tests/ignore_root_user_error/.gitignore b/tests/ignore_root_user_error/.gitignore
new file mode 100644
index 0000000..ac51a05
--- /dev/null
+++ b/tests/ignore_root_user_error/.gitignore
@@ -0,0 +1 @@
+bazel-*
diff --git a/tests/ignore_root_user_error/BUILD.bazel b/tests/ignore_root_user_error/BUILD.bazel
new file mode 100644
index 0000000..f907624
--- /dev/null
+++ b/tests/ignore_root_user_error/BUILD.bazel
@@ -0,0 +1,7 @@
+load("@rules_python//python:defs.bzl", "py_test")
+
+py_test(
+ name = "foo_test",
+ srcs = ["foo_test.py"],
+ visibility = ["//visibility:public"],
+)
diff --git a/tests/ignore_root_user_error/README.md b/tests/ignore_root_user_error/README.md
new file mode 100644
index 0000000..47da5eb
--- /dev/null
+++ b/tests/ignore_root_user_error/README.md
@@ -0,0 +1,2 @@
+# ignore_root_user_errors
+There are cases when we have to run Python targets with root, e.g., in Docker containers, requiring setting `ignore_root_user_error = True` when registering Python toolchain. This test makes sure that rules_python works in this case. \ No newline at end of file
diff --git a/tests/ignore_root_user_error/WORKSPACE b/tests/ignore_root_user_error/WORKSPACE
new file mode 100644
index 0000000..e0528e4
--- /dev/null
+++ b/tests/ignore_root_user_error/WORKSPACE
@@ -0,0 +1,27 @@
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+
+python_register_toolchains(
+ name = "python39",
+ ignore_root_user_error = True,
+ python_version = "3.9",
+)
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+http_archive(
+ name = "bazel_skylib",
+ sha256 = "c6966ec828da198c5d9adbaa94c05e3a1c7f21bd012a0b29ba8ddbccb2c93b0d",
+ urls = [
+ "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ "https://github.com/bazelbuild/bazel-skylib/releases/download/1.1.1/bazel-skylib-1.1.1.tar.gz",
+ ],
+)
+
+load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace")
+
+bazel_skylib_workspace()
diff --git a/tests/ignore_root_user_error/foo_test.py b/tests/ignore_root_user_error/foo_test.py
new file mode 100644
index 0000000..724cdcb
--- /dev/null
+++ b/tests/ignore_root_user_error/foo_test.py
@@ -0,0 +1,13 @@
+# Copyright 2019 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.
diff --git a/tests/load_from_macro/BUILD.bazel b/tests/load_from_macro/BUILD.bazel
new file mode 100644
index 0000000..00d7bf9
--- /dev/null
+++ b/tests/load_from_macro/BUILD.bazel
@@ -0,0 +1,34 @@
+# Copyright 2019 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")
+load(":tags.bzl", "TAGS")
+
+licenses(["notice"])
+
+py_library(
+ name = "foo",
+ srcs = ["foo.py"],
+ tags = TAGS,
+ # Allow a test to verify an "outside package" doesn't get included
+ visibility = ["//examples/wheel:__pkg__"],
+)
+
+genrule(
+ name = "test_current_py_toolchain",
+ srcs = [],
+ outs = ["out.txt"],
+ cmd = "$(PYTHON3) --version > $(location out.txt)",
+ toolchains = ["//python:current_py_toolchain"],
+)
diff --git a/tests/load_from_macro/foo.py b/tests/load_from_macro/foo.py
new file mode 100644
index 0000000..724cdcb
--- /dev/null
+++ b/tests/load_from_macro/foo.py
@@ -0,0 +1,13 @@
+# Copyright 2019 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.
diff --git a/tests/load_from_macro/tags.bzl b/tests/load_from_macro/tags.bzl
new file mode 100644
index 0000000..18c10e0
--- /dev/null
+++ b/tests/load_from_macro/tags.bzl
@@ -0,0 +1,18 @@
+# Copyright 2019 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.
+
+"""
+Example tags defined in a separate file.
+"""
+TAGS = ["first_tag", "second_tag"]
diff --git a/tests/pip_hub_repository/normalize_name/BUILD.bazel b/tests/pip_hub_repository/normalize_name/BUILD.bazel
new file mode 100644
index 0000000..3aa3b00
--- /dev/null
+++ b/tests/pip_hub_repository/normalize_name/BUILD.bazel
@@ -0,0 +1,3 @@
+load(":normalize_name_tests.bzl", "normalize_name_test_suite")
+
+normalize_name_test_suite(name = "normalize_name_tests")
diff --git a/tests/pip_hub_repository/normalize_name/normalize_name_tests.bzl b/tests/pip_hub_repository/normalize_name/normalize_name_tests.bzl
new file mode 100644
index 0000000..0c94567
--- /dev/null
+++ b/tests/pip_hub_repository/normalize_name/normalize_name_tests.bzl
@@ -0,0 +1,50 @@
+# 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.
+
+""
+
+load("@rules_testing//lib:test_suite.bzl", "test_suite")
+load("//python/private:normalize_name.bzl", "normalize_name") # buildifier: disable=bzl-visibility
+
+_tests = []
+
+def _test_name_normalization(env):
+ want = {
+ input: "friendly_bard"
+ for input in [
+ "friendly-bard",
+ "Friendly-Bard",
+ "FRIENDLY-BARD",
+ "friendly.bard",
+ "friendly_bard",
+ "friendly--bard",
+ "FrIeNdLy-._.-bArD",
+ ]
+ }
+
+ actual = {
+ input: normalize_name(input)
+ for input in want.keys()
+ }
+ env.expect.that_dict(actual).contains_exactly(want)
+
+_tests.append(_test_name_normalization)
+
+def normalize_name_test_suite(name):
+ """Create the test suite.
+
+ Args:
+ name: the name of the test suite
+ """
+ test_suite(name = name, basic_tests = _tests)
diff --git a/tests/pip_repository_entry_points/.bazelrc b/tests/pip_repository_entry_points/.bazelrc
new file mode 100644
index 0000000..b9c4c27
--- /dev/null
+++ b/tests/pip_repository_entry_points/.bazelrc
@@ -0,0 +1,7 @@
+# Bazel configuration flags
+
+build --enable_runfiles
+startup --windows_enable_symlinks
+
+# https://docs.bazel.build/versions/main/best-practices.html#using-the-bazelrc-file
+try-import %workspace%/user.bazelrc
diff --git a/tests/pip_repository_entry_points/.gitignore b/tests/pip_repository_entry_points/.gitignore
new file mode 100644
index 0000000..e5ae073
--- /dev/null
+++ b/tests/pip_repository_entry_points/.gitignore
@@ -0,0 +1,4 @@
+# git ignore patterns
+
+/bazel-*
+user.bazelrc
diff --git a/tests/pip_repository_entry_points/BUILD.bazel b/tests/pip_repository_entry_points/BUILD.bazel
new file mode 100644
index 0000000..81c01c3
--- /dev/null
+++ b/tests/pip_repository_entry_points/BUILD.bazel
@@ -0,0 +1,55 @@
+load("@pip_installed//:requirements.bzl", installed_entry_point = "entry_point")
+load("@pip_parsed//:requirements.bzl", parsed_entry_point = "entry_point")
+load("@rules_python//python:defs.bzl", "py_test")
+load("@rules_python//python:pip.bzl", "compile_pip_requirements")
+
+# This rule adds a convenient way to update the requirements file.
+compile_pip_requirements(
+ name = "requirements",
+ extra_args = ["--allow-unsafe"],
+ requirements_windows = ":requirements_windows.txt",
+)
+
+pip_parsed_sphinx = parsed_entry_point(
+ pkg = "sphinx",
+ script = "sphinx-build",
+)
+
+pip_parsed_yamllint = parsed_entry_point("yamllint")
+
+py_test(
+ name = "pip_parse_entry_points_test",
+ srcs = ["pip_repository_entry_points_test.py"],
+ data = [
+ pip_parsed_sphinx,
+ pip_parsed_yamllint,
+ ],
+ env = {
+ "SPHINX_BUILD_ENTRY_POINT": "$(rootpath {})".format(pip_parsed_sphinx),
+ "YAMLLINT_ENTRY_POINT": "$(rootpath {})".format(pip_parsed_yamllint),
+ },
+ main = "pip_repository_entry_points_test.py",
+ deps = ["@rules_python//python/runfiles"],
+)
+
+pip_installed_sphinx = installed_entry_point(
+ pkg = "sphinx",
+ script = "sphinx-build",
+)
+
+pip_installed_yamllint = installed_entry_point("yamllint")
+
+py_test(
+ name = "pip_install_annotations_test",
+ srcs = ["pip_repository_entry_points_test.py"],
+ data = [
+ pip_installed_sphinx,
+ pip_installed_yamllint,
+ ],
+ env = {
+ "SPHINX_BUILD_ENTRY_POINT": "$(rootpath {})".format(pip_installed_sphinx),
+ "YAMLLINT_ENTRY_POINT": "$(rootpath {})".format(pip_installed_yamllint),
+ },
+ main = "pip_repository_entry_points_test.py",
+ deps = ["@rules_python//python/runfiles"],
+)
diff --git a/tests/pip_repository_entry_points/WORKSPACE b/tests/pip_repository_entry_points/WORKSPACE
new file mode 100644
index 0000000..1afd68c
--- /dev/null
+++ b/tests/pip_repository_entry_points/WORKSPACE
@@ -0,0 +1,44 @@
+workspace(name = "pip_repository_annotations_example")
+
+local_repository(
+ name = "rules_python",
+ path = "../..",
+)
+
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
+
+# This toolchain is explicitly 3.10 while `rules_python` is 3.9 to act as
+# a regression test, ensuring 3.10 is functional
+python_register_toolchains(
+ name = "python310",
+ python_version = "3.10",
+)
+
+load("@python310//:defs.bzl", "interpreter")
+load("@rules_python//python:pip.bzl", "pip_install", "pip_parse")
+
+# For a more thorough example of `pip_parse`. See `@rules_python//examples/pip_parse`
+pip_parse(
+ name = "pip_parsed",
+ python_interpreter_target = interpreter,
+ requirements_lock = "//:requirements.txt",
+ requirements_windows = "//:requirements_windows.txt",
+)
+
+load("@pip_parsed//:requirements.bzl", install_pip_parse_deps = "install_deps")
+
+install_pip_parse_deps()
+
+# For a more thorough example of `pip_install`. See `@rules_python//examples/pip_install`
+pip_install(
+ name = "pip_installed",
+ python_interpreter_target = interpreter,
+ requirements = "//:requirements.txt",
+ requirements_windows = "//:requirements_windows.txt",
+)
+
+load("@pip_installed//:requirements.bzl", install_pip_install_deps = "install_deps")
+
+install_pip_install_deps()
diff --git a/tests/pip_repository_entry_points/pip_repository_entry_points_test.py b/tests/pip_repository_entry_points/pip_repository_entry_points_test.py
new file mode 100644
index 0000000..0375153
--- /dev/null
+++ b/tests/pip_repository_entry_points/pip_repository_entry_points_test.py
@@ -0,0 +1,83 @@
+#!/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.
+
+
+import os
+import subprocess
+import unittest
+from pathlib import Path
+
+from rules_python.python.runfiles import runfiles
+
+
+class PipRepositoryEntryPointsTest(unittest.TestCase):
+ maxDiff = None
+
+ def test_entry_point_void_return(self):
+ env = os.environ.get("YAMLLINT_ENTRY_POINT")
+ self.assertIsNotNone(env)
+
+ r = runfiles.Create()
+ entry_point = Path(r.Rlocation(str(Path(*Path(env).parts[1:]))))
+ self.assertTrue(entry_point.exists())
+
+ proc = subprocess.run(
+ [str(entry_point), "--version"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.28.0")
+
+ # yamllint entry_point is of the form `def run(argv=None):`
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ subprocess.run(
+ [str(entry_point), "--option-does-not-exist"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertIn("returned non-zero exit status 2", str(context.exception))
+
+ def test_entry_point_int_return(self):
+ env = os.environ.get("SPHINX_BUILD_ENTRY_POINT")
+ self.assertIsNotNone(env)
+
+ r = runfiles.Create()
+ entry_point = Path(r.Rlocation(str(Path(*Path(env).parts[1:]))))
+ self.assertTrue(entry_point.exists())
+
+ proc = subprocess.run(
+ [str(entry_point), "--version"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ # sphinx-build uses args[0] for its name, only assert the version here
+ self.assertTrue(proc.stdout.decode("utf-8").strip().endswith("4.3.2"))
+
+ # sphinx-build entry_point is of the form `def main(argv: List[str] = sys.argv[1:]) -> int:`
+ with self.assertRaises(subprocess.CalledProcessError) as context:
+ subprocess.run(
+ [entry_point, "--option-does-not-exist"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertIn("returned non-zero exit status 2", str(context.exception))
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/pip_repository_entry_points/requirements.in b/tests/pip_repository_entry_points/requirements.in
new file mode 100644
index 0000000..2cc4625
--- /dev/null
+++ b/tests/pip_repository_entry_points/requirements.in
@@ -0,0 +1,5 @@
+sphinx==4.3.2
+yamllint>=1.28.0
+
+# Last avialable for ubuntu python3.6
+setuptools==59.6.0
diff --git a/tests/pip_repository_entry_points/requirements.txt b/tests/pip_repository_entry_points/requirements.txt
new file mode 100644
index 0000000..a93facc
--- /dev/null
+++ b/tests/pip_repository_entry_points/requirements.txt
@@ -0,0 +1,215 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+alabaster==0.7.12 \
+ --hash=sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359 \
+ --hash=sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02
+ # via sphinx
+babel==2.9.1 \
+ --hash=sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9 \
+ --hash=sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0
+ # via sphinx
+certifi==2021.10.8 \
+ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
+ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
+ # via requests
+charset-normalizer==2.0.10 \
+ --hash=sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd \
+ --hash=sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455
+ # via requests
+docutils==0.17.1 \
+ --hash=sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125 \
+ --hash=sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61
+ # via sphinx
+idna==3.3 \
+ --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
+ --hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d
+ # via requests
+imagesize==1.3.0 \
+ --hash=sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c \
+ --hash=sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d
+ # via sphinx
+jinja2==3.0.3 \
+ --hash=sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 \
+ --hash=sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7
+ # via sphinx
+markupsafe==2.0.1 \
+ --hash=sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298 \
+ --hash=sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64 \
+ --hash=sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b \
+ --hash=sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194 \
+ --hash=sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567 \
+ --hash=sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff \
+ --hash=sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724 \
+ --hash=sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74 \
+ --hash=sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646 \
+ --hash=sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35 \
+ --hash=sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6 \
+ --hash=sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a \
+ --hash=sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6 \
+ --hash=sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad \
+ --hash=sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26 \
+ --hash=sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38 \
+ --hash=sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac \
+ --hash=sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7 \
+ --hash=sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6 \
+ --hash=sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047 \
+ --hash=sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75 \
+ --hash=sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f \
+ --hash=sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b \
+ --hash=sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135 \
+ --hash=sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8 \
+ --hash=sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a \
+ --hash=sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a \
+ --hash=sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1 \
+ --hash=sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9 \
+ --hash=sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864 \
+ --hash=sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914 \
+ --hash=sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee \
+ --hash=sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f \
+ --hash=sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18 \
+ --hash=sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8 \
+ --hash=sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2 \
+ --hash=sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d \
+ --hash=sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b \
+ --hash=sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b \
+ --hash=sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86 \
+ --hash=sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6 \
+ --hash=sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f \
+ --hash=sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb \
+ --hash=sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833 \
+ --hash=sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28 \
+ --hash=sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e \
+ --hash=sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415 \
+ --hash=sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902 \
+ --hash=sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f \
+ --hash=sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d \
+ --hash=sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9 \
+ --hash=sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d \
+ --hash=sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145 \
+ --hash=sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066 \
+ --hash=sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c \
+ --hash=sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1 \
+ --hash=sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a \
+ --hash=sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207 \
+ --hash=sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f \
+ --hash=sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53 \
+ --hash=sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd \
+ --hash=sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134 \
+ --hash=sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85 \
+ --hash=sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9 \
+ --hash=sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5 \
+ --hash=sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94 \
+ --hash=sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509 \
+ --hash=sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51 \
+ --hash=sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872
+ # via jinja2
+packaging==21.3 \
+ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \
+ --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522
+ # via sphinx
+pathspec==0.9.0 \
+ --hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
+ --hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
+ # via yamllint
+pygments==2.11.2 \
+ --hash=sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65 \
+ --hash=sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a
+ # via sphinx
+pyparsing==3.0.6 \
+ --hash=sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4 \
+ --hash=sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81
+ # via packaging
+pytz==2021.3 \
+ --hash=sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c \
+ --hash=sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326
+ # via babel
+pyyaml==6.0 \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.27.1 \
+ --hash=sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61 \
+ --hash=sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d
+ # via sphinx
+setuptools==59.6.0 \
+ --hash=sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373 \
+ --hash=sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e
+ # via
+ # -r requirements.in
+ # sphinx
+ # yamllint
+snowballstemmer==2.2.0 \
+ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
+ --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
+ # via sphinx
+sphinx==4.3.2 \
+ --hash=sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c \
+ --hash=sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851
+ # via -r requirements.in
+sphinxcontrib-applehelp==1.0.2 \
+ --hash=sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a \
+ --hash=sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58
+ # via sphinx
+sphinxcontrib-devhelp==1.0.2 \
+ --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \
+ --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4
+ # via sphinx
+sphinxcontrib-htmlhelp==2.0.0 \
+ --hash=sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07 \
+ --hash=sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2
+ # via sphinx
+sphinxcontrib-jsmath==1.0.1 \
+ --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \
+ --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8
+ # via sphinx
+sphinxcontrib-qthelp==1.0.3 \
+ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \
+ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6
+ # via sphinx
+sphinxcontrib-serializinghtml==1.1.5 \
+ --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \
+ --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952
+ # via sphinx
+urllib3==1.26.7 \
+ --hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece \
+ --hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844
+ # via requests
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
+ # via -r requirements.in
diff --git a/tests/pip_repository_entry_points/requirements_windows.txt b/tests/pip_repository_entry_points/requirements_windows.txt
new file mode 100644
index 0000000..651e2b5
--- /dev/null
+++ b/tests/pip_repository_entry_points/requirements_windows.txt
@@ -0,0 +1,219 @@
+#
+# This file is autogenerated by pip-compile with Python 3.10
+# by the following command:
+#
+# bazel run //:requirements.update
+#
+alabaster==0.7.12 \
+ --hash=sha256:446438bdcca0e05bd45ea2de1668c1d9b032e1a9154c2c259092d77031ddd359 \
+ --hash=sha256:a661d72d58e6ea8a57f7a86e37d86716863ee5e92788398526d58b26a4e4dc02
+ # via sphinx
+babel==2.9.1 \
+ --hash=sha256:ab49e12b91d937cd11f0b67cb259a57ab4ad2b59ac7a3b41d6c06c0ac5b0def9 \
+ --hash=sha256:bc0c176f9f6a994582230df350aa6e05ba2ebe4b3ac317eab29d9be5d2768da0
+ # via sphinx
+certifi==2021.10.8 \
+ --hash=sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872 \
+ --hash=sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569
+ # via requests
+charset-normalizer==2.0.10 \
+ --hash=sha256:876d180e9d7432c5d1dfd4c5d26b72f099d503e8fcc0feb7532c9289be60fcbd \
+ --hash=sha256:cb957888737fc0bbcd78e3df769addb41fd1ff8cf950dc9e7ad7793f1bf44455
+ # via requests
+colorama==0.4.6 \
+ --hash=sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44 \
+ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6
+ # via sphinx
+docutils==0.17.1 \
+ --hash=sha256:686577d2e4c32380bb50cbb22f575ed742d58168cee37e99117a854bcd88f125 \
+ --hash=sha256:cf316c8370a737a022b72b56874f6602acf974a37a9fba42ec2876387549fc61
+ # via sphinx
+idna==3.3 \
+ --hash=sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff \
+ --hash=sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d
+ # via requests
+imagesize==1.3.0 \
+ --hash=sha256:1db2f82529e53c3e929e8926a1fa9235aa82d0bd0c580359c67ec31b2fddaa8c \
+ --hash=sha256:cd1750d452385ca327479d45b64d9c7729ecf0b3969a58148298c77092261f9d
+ # via sphinx
+jinja2==3.0.3 \
+ --hash=sha256:077ce6014f7b40d03b47d1f1ca4b0fc8328a692bd284016f806ed0eaca390ad8 \
+ --hash=sha256:611bb273cd68f3b993fabdc4064fc858c5b47a973cb5aa7999ec1ba405c87cd7
+ # via sphinx
+markupsafe==2.0.1 \
+ --hash=sha256:01a9b8ea66f1658938f65b93a85ebe8bc016e6769611be228d797c9d998dd298 \
+ --hash=sha256:023cb26ec21ece8dc3907c0e8320058b2e0cb3c55cf9564da612bc325bed5e64 \
+ --hash=sha256:0446679737af14f45767963a1a9ef7620189912317d095f2d9ffa183a4d25d2b \
+ --hash=sha256:04635854b943835a6ea959e948d19dcd311762c5c0c6e1f0e16ee57022669194 \
+ --hash=sha256:0717a7390a68be14b8c793ba258e075c6f4ca819f15edfc2a3a027c823718567 \
+ --hash=sha256:0955295dd5eec6cb6cc2fe1698f4c6d84af2e92de33fbcac4111913cd100a6ff \
+ --hash=sha256:0d4b31cc67ab36e3392bbf3862cfbadac3db12bdd8b02a2731f509ed5b829724 \
+ --hash=sha256:10f82115e21dc0dfec9ab5c0223652f7197feb168c940f3ef61563fc2d6beb74 \
+ --hash=sha256:168cd0a3642de83558a5153c8bd34f175a9a6e7f6dc6384b9655d2697312a646 \
+ --hash=sha256:1d609f577dc6e1aa17d746f8bd3c31aa4d258f4070d61b2aa5c4166c1539de35 \
+ --hash=sha256:1f2ade76b9903f39aa442b4aadd2177decb66525062db244b35d71d0ee8599b6 \
+ --hash=sha256:20dca64a3ef2d6e4d5d615a3fd418ad3bde77a47ec8a23d984a12b5b4c74491a \
+ --hash=sha256:2a7d351cbd8cfeb19ca00de495e224dea7e7d919659c2841bbb7f420ad03e2d6 \
+ --hash=sha256:2d7d807855b419fc2ed3e631034685db6079889a1f01d5d9dac950f764da3dad \
+ --hash=sha256:2ef54abee730b502252bcdf31b10dacb0a416229b72c18b19e24a4509f273d26 \
+ --hash=sha256:36bc903cbb393720fad60fc28c10de6acf10dc6cc883f3e24ee4012371399a38 \
+ --hash=sha256:37205cac2a79194e3750b0af2a5720d95f786a55ce7df90c3af697bfa100eaac \
+ --hash=sha256:3c112550557578c26af18a1ccc9e090bfe03832ae994343cfdacd287db6a6ae7 \
+ --hash=sha256:3dd007d54ee88b46be476e293f48c85048603f5f516008bee124ddd891398ed6 \
+ --hash=sha256:4296f2b1ce8c86a6aea78613c34bb1a672ea0e3de9c6ba08a960efe0b0a09047 \
+ --hash=sha256:47ab1e7b91c098ab893b828deafa1203de86d0bc6ab587b160f78fe6c4011f75 \
+ --hash=sha256:49e3ceeabbfb9d66c3aef5af3a60cc43b85c33df25ce03d0031a608b0a8b2e3f \
+ --hash=sha256:4dc8f9fb58f7364b63fd9f85013b780ef83c11857ae79f2feda41e270468dd9b \
+ --hash=sha256:4efca8f86c54b22348a5467704e3fec767b2db12fc39c6d963168ab1d3fc9135 \
+ --hash=sha256:53edb4da6925ad13c07b6d26c2a852bd81e364f95301c66e930ab2aef5b5ddd8 \
+ --hash=sha256:5855f8438a7d1d458206a2466bf82b0f104a3724bf96a1c781ab731e4201731a \
+ --hash=sha256:594c67807fb16238b30c44bdf74f36c02cdf22d1c8cda91ef8a0ed8dabf5620a \
+ --hash=sha256:5b6d930f030f8ed98e3e6c98ffa0652bdb82601e7a016ec2ab5d7ff23baa78d1 \
+ --hash=sha256:5bb28c636d87e840583ee3adeb78172efc47c8b26127267f54a9c0ec251d41a9 \
+ --hash=sha256:60bf42e36abfaf9aff1f50f52644b336d4f0a3fd6d8a60ca0d054ac9f713a864 \
+ --hash=sha256:611d1ad9a4288cf3e3c16014564df047fe08410e628f89805e475368bd304914 \
+ --hash=sha256:6300b8454aa6930a24b9618fbb54b5a68135092bc666f7b06901f897fa5c2fee \
+ --hash=sha256:63f3268ba69ace99cab4e3e3b5840b03340efed0948ab8f78d2fd87ee5442a4f \
+ --hash=sha256:6557b31b5e2c9ddf0de32a691f2312a32f77cd7681d8af66c2692efdbef84c18 \
+ --hash=sha256:693ce3f9e70a6cf7d2fb9e6c9d8b204b6b39897a2c4a1aa65728d5ac97dcc1d8 \
+ --hash=sha256:6a7fae0dd14cf60ad5ff42baa2e95727c3d81ded453457771d02b7d2b3f9c0c2 \
+ --hash=sha256:6c4ca60fa24e85fe25b912b01e62cb969d69a23a5d5867682dd3e80b5b02581d \
+ --hash=sha256:6fcf051089389abe060c9cd7caa212c707e58153afa2c649f00346ce6d260f1b \
+ --hash=sha256:7d91275b0245b1da4d4cfa07e0faedd5b0812efc15b702576d103293e252af1b \
+ --hash=sha256:89c687013cb1cd489a0f0ac24febe8c7a666e6e221b783e53ac50ebf68e45d86 \
+ --hash=sha256:8d206346619592c6200148b01a2142798c989edcb9c896f9ac9722a99d4e77e6 \
+ --hash=sha256:905fec760bd2fa1388bb5b489ee8ee5f7291d692638ea5f67982d968366bef9f \
+ --hash=sha256:97383d78eb34da7e1fa37dd273c20ad4320929af65d156e35a5e2d89566d9dfb \
+ --hash=sha256:984d76483eb32f1bcb536dc27e4ad56bba4baa70be32fa87152832cdd9db0833 \
+ --hash=sha256:99df47edb6bda1249d3e80fdabb1dab8c08ef3975f69aed437cb69d0a5de1e28 \
+ --hash=sha256:9f02365d4e99430a12647f09b6cc8bab61a6564363f313126f775eb4f6ef798e \
+ --hash=sha256:a30e67a65b53ea0a5e62fe23682cfe22712e01f453b95233b25502f7c61cb415 \
+ --hash=sha256:ab3ef638ace319fa26553db0624c4699e31a28bb2a835c5faca8f8acf6a5a902 \
+ --hash=sha256:aca6377c0cb8a8253e493c6b451565ac77e98c2951c45f913e0b52facdcff83f \
+ --hash=sha256:add36cb2dbb8b736611303cd3bfcee00afd96471b09cda130da3581cbdc56a6d \
+ --hash=sha256:b2f4bf27480f5e5e8ce285a8c8fd176c0b03e93dcc6646477d4630e83440c6a9 \
+ --hash=sha256:b7f2d075102dc8c794cbde1947378051c4e5180d52d276987b8d28a3bd58c17d \
+ --hash=sha256:baa1a4e8f868845af802979fcdbf0bb11f94f1cb7ced4c4b8a351bb60d108145 \
+ --hash=sha256:be98f628055368795d818ebf93da628541e10b75b41c559fdf36d104c5787066 \
+ --hash=sha256:bf5d821ffabf0ef3533c39c518f3357b171a1651c1ff6827325e4489b0e46c3c \
+ --hash=sha256:c47adbc92fc1bb2b3274c4b3a43ae0e4573d9fbff4f54cd484555edbf030baf1 \
+ --hash=sha256:cdfba22ea2f0029c9261a4bd07e830a8da012291fbe44dc794e488b6c9bb353a \
+ --hash=sha256:d6c7ebd4e944c85e2c3421e612a7057a2f48d478d79e61800d81468a8d842207 \
+ --hash=sha256:d7f9850398e85aba693bb640262d3611788b1f29a79f0c93c565694658f4071f \
+ --hash=sha256:d8446c54dc28c01e5a2dbac5a25f071f6653e6e40f3a8818e8b45d790fe6ef53 \
+ --hash=sha256:deb993cacb280823246a026e3b2d81c493c53de6acfd5e6bfe31ab3402bb37dd \
+ --hash=sha256:e0f138900af21926a02425cf736db95be9f4af72ba1bb21453432a07f6082134 \
+ --hash=sha256:e9936f0b261d4df76ad22f8fee3ae83b60d7c3e871292cd42f40b81b70afae85 \
+ --hash=sha256:f0567c4dc99f264f49fe27da5f735f414c4e7e7dd850cfd8e69f0862d7c74ea9 \
+ --hash=sha256:f5653a225f31e113b152e56f154ccbe59eeb1c7487b39b9d9f9cdb58e6c79dc5 \
+ --hash=sha256:f826e31d18b516f653fe296d967d700fddad5901ae07c622bb3705955e1faa94 \
+ --hash=sha256:f8ba0e8349a38d3001fae7eadded3f6606f0da5d748ee53cc1dab1d6527b9509 \
+ --hash=sha256:f9081981fe268bd86831e5c75f7de206ef275defcb82bc70740ae6dc507aee51 \
+ --hash=sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872
+ # via jinja2
+packaging==21.3 \
+ --hash=sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb \
+ --hash=sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522
+ # via sphinx
+pathspec==0.9.0 \
+ --hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
+ --hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
+ # via yamllint
+pygments==2.11.2 \
+ --hash=sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65 \
+ --hash=sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a
+ # via sphinx
+pyparsing==3.0.6 \
+ --hash=sha256:04ff808a5b90911829c55c4e26f75fa5ca8a2f5f36aa3a51f68e27033341d3e4 \
+ --hash=sha256:d9bdec0013ef1eb5a84ab39a3b3868911598afa494f5faa038647101504e2b81
+ # via packaging
+pytz==2021.3 \
+ --hash=sha256:3672058bc3453457b622aab7a1c3bfd5ab0bdae451512f6cf25f64ed37f5b87c \
+ --hash=sha256:acad2d8b20a1af07d4e4c9d2e9285c5ed9104354062f275f3fcd88dcef4f1326
+ # via babel
+pyyaml==6.0 \
+ --hash=sha256:0283c35a6a9fbf047493e3a0ce8d79ef5030852c51e9d911a27badfde0605293 \
+ --hash=sha256:055d937d65826939cb044fc8c9b08889e8c743fdc6a32b33e2390f66013e449b \
+ --hash=sha256:07751360502caac1c067a8132d150cf3d61339af5691fe9e87803040dbc5db57 \
+ --hash=sha256:0b4624f379dab24d3725ffde76559cff63d9ec94e1736b556dacdfebe5ab6d4b \
+ --hash=sha256:0ce82d761c532fe4ec3f87fc45688bdd3a4c1dc5e0b4a19814b9009a29baefd4 \
+ --hash=sha256:1e4747bc279b4f613a09eb64bba2ba602d8a6664c6ce6396a4d0cd413a50ce07 \
+ --hash=sha256:213c60cd50106436cc818accf5baa1aba61c0189ff610f64f4a3e8c6726218ba \
+ --hash=sha256:231710d57adfd809ef5d34183b8ed1eeae3f76459c18fb4a0b373ad56bedcdd9 \
+ --hash=sha256:277a0ef2981ca40581a47093e9e2d13b3f1fbbeffae064c1d21bfceba2030287 \
+ --hash=sha256:2cd5df3de48857ed0544b34e2d40e9fac445930039f3cfe4bcc592a1f836d513 \
+ --hash=sha256:40527857252b61eacd1d9af500c3337ba8deb8fc298940291486c465c8b46ec0 \
+ --hash=sha256:473f9edb243cb1935ab5a084eb238d842fb8f404ed2193a915d1784b5a6b5fc0 \
+ --hash=sha256:48c346915c114f5fdb3ead70312bd042a953a8ce5c7106d5bfb1a5254e47da92 \
+ --hash=sha256:50602afada6d6cbfad699b0c7bb50d5ccffa7e46a3d738092afddc1f9758427f \
+ --hash=sha256:68fb519c14306fec9720a2a5b45bc9f0c8d1b9c72adf45c37baedfcd949c35a2 \
+ --hash=sha256:77f396e6ef4c73fdc33a9157446466f1cff553d979bd00ecb64385760c6babdc \
+ --hash=sha256:819b3830a1543db06c4d4b865e70ded25be52a2e0631ccd2f6a47a2822f2fd7c \
+ --hash=sha256:897b80890765f037df3403d22bab41627ca8811ae55e9a722fd0392850ec4d86 \
+ --hash=sha256:98c4d36e99714e55cfbaaee6dd5badbc9a1ec339ebfc3b1f52e293aee6bb71a4 \
+ --hash=sha256:9df7ed3b3d2e0ecfe09e14741b857df43adb5a3ddadc919a2d94fbdf78fea53c \
+ --hash=sha256:9fa600030013c4de8165339db93d182b9431076eb98eb40ee068700c9c813e34 \
+ --hash=sha256:a80a78046a72361de73f8f395f1f1e49f956c6be882eed58505a15f3e430962b \
+ --hash=sha256:b3d267842bf12586ba6c734f89d1f5b871df0273157918b0ccefa29deb05c21c \
+ --hash=sha256:b5b9eccad747aabaaffbc6064800670f0c297e52c12754eb1d976c57e4f74dcb \
+ --hash=sha256:c5687b8d43cf58545ade1fe3e055f70eac7a5a1a0bf42824308d868289a95737 \
+ --hash=sha256:cba8c411ef271aa037d7357a2bc8f9ee8b58b9965831d9e51baf703280dc73d3 \
+ --hash=sha256:d15a181d1ecd0d4270dc32edb46f7cb7733c7c508857278d3d378d14d606db2d \
+ --hash=sha256:d4db7c7aef085872ef65a8fd7d6d09a14ae91f691dec3e87ee5ee0539d516f53 \
+ --hash=sha256:d4eccecf9adf6fbcc6861a38015c2a64f38b9d94838ac1810a9023a0609e1b78 \
+ --hash=sha256:d67d839ede4ed1b28a4e8909735fc992a923cdb84e618544973d7dfc71540803 \
+ --hash=sha256:daf496c58a8c52083df09b80c860005194014c3698698d1a57cbcfa182142a3a \
+ --hash=sha256:e61ceaab6f49fb8bdfaa0f92c4b57bcfbea54c09277b1b4f7ac376bfb7a7c174 \
+ --hash=sha256:f84fbc98b019fef2ee9a1cb3ce93e3187a6df0b2538a651bfb890254ba9f90b5
+ # via yamllint
+requests==2.27.1 \
+ --hash=sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61 \
+ --hash=sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d
+ # via sphinx
+setuptools==59.6.0 \
+ --hash=sha256:22c7348c6d2976a52632c67f7ab0cdf40147db7789f9aed18734643fe9cf3373 \
+ --hash=sha256:4ce92f1e1f8f01233ee9952c04f6b81d1e02939d6e1b488428154974a4d0783e
+ # via
+ # -r requirements.in
+ # sphinx
+ # yamllint
+snowballstemmer==2.2.0 \
+ --hash=sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1 \
+ --hash=sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a
+ # via sphinx
+sphinx==4.3.2 \
+ --hash=sha256:0a8836751a68306b3fe97ecbe44db786f8479c3bf4b80e3a7f5c838657b4698c \
+ --hash=sha256:6a11ea5dd0bdb197f9c2abc2e0ce73e01340464feaece525e64036546d24c851
+ # via -r requirements.in
+sphinxcontrib-applehelp==1.0.2 \
+ --hash=sha256:806111e5e962be97c29ec4c1e7fe277bfd19e9652fb1a4392105b43e01af885a \
+ --hash=sha256:a072735ec80e7675e3f432fcae8610ecf509c5f1869d17e2eecff44389cdbc58
+ # via sphinx
+sphinxcontrib-devhelp==1.0.2 \
+ --hash=sha256:8165223f9a335cc1af7ffe1ed31d2871f325254c0423bc0c4c7cd1c1e4734a2e \
+ --hash=sha256:ff7f1afa7b9642e7060379360a67e9c41e8f3121f2ce9164266f61b9f4b338e4
+ # via sphinx
+sphinxcontrib-htmlhelp==2.0.0 \
+ --hash=sha256:d412243dfb797ae3ec2b59eca0e52dac12e75a241bf0e4eb861e450d06c6ed07 \
+ --hash=sha256:f5f8bb2d0d629f398bf47d0d69c07bc13b65f75a81ad9e2f71a63d4b7a2f6db2
+ # via sphinx
+sphinxcontrib-jsmath==1.0.1 \
+ --hash=sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178 \
+ --hash=sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8
+ # via sphinx
+sphinxcontrib-qthelp==1.0.3 \
+ --hash=sha256:4c33767ee058b70dba89a6fc5c1892c0d57a54be67ddd3e7875a18d14cba5a72 \
+ --hash=sha256:bd9fc24bcb748a8d51fd4ecaade681350aa63009a347a8c14e637895444dfab6
+ # via sphinx
+sphinxcontrib-serializinghtml==1.1.5 \
+ --hash=sha256:352a9a00ae864471d3a7ead8d7d79f5fc0b57e8b3f95e9867eb9eb28999b92fd \
+ --hash=sha256:aa5f6de5dfdf809ef505c4895e51ef5c9eac17d0f287933eb49ec495280b6952
+ # via sphinx
+urllib3==1.26.7 \
+ --hash=sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece \
+ --hash=sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844
+ # via requests
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
+ # via -r requirements.in
diff --git a/tests/py_cc_toolchain_info_subject.bzl b/tests/py_cc_toolchain_info_subject.bzl
new file mode 100644
index 0000000..ab9d1b8
--- /dev/null
+++ b/tests/py_cc_toolchain_info_subject.bzl
@@ -0,0 +1,47 @@
+# 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.
+"""PyCcToolchainInfo testing subject."""
+
+load("@rules_testing//lib:truth.bzl", "subjects")
+
+def _py_cc_toolchain_info_subject_new(info, *, meta):
+ # buildifier: disable=uninitialized
+ public = struct(
+ headers = lambda *a, **k: _py_cc_toolchain_info_subject_headers(self, *a, **k),
+ python_version = lambda *a, **k: _py_cc_toolchain_info_subject_python_version(self, *a, **k),
+ actual = info,
+ )
+ self = struct(actual = info, meta = meta)
+ return public
+
+def _py_cc_toolchain_info_subject_headers(self):
+ return subjects.struct(
+ self.actual.headers,
+ meta = self.meta.derive("headers()"),
+ attrs = dict(
+ providers_map = subjects.dict,
+ ),
+ )
+
+def _py_cc_toolchain_info_subject_python_version(self):
+ return subjects.str(
+ self.actual.python_version,
+ meta = self.meta.derive("python_version()"),
+ )
+
+# Disable this to aid doc generation
+# buildifier: disable=name-conventions
+PyCcToolchainInfoSubject = struct(
+ new = _py_cc_toolchain_info_subject_new,
+)
diff --git a/tests/py_wheel/BUILD.bazel b/tests/py_wheel/BUILD.bazel
new file mode 100644
index 0000000..d925bb9
--- /dev/null
+++ b/tests/py_wheel/BUILD.bazel
@@ -0,0 +1,18 @@
+# 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.
+"""Tests for py_wheel."""
+
+load(":py_wheel_tests.bzl", "py_wheel_test_suite")
+
+py_wheel_test_suite(name = "py_wheel_tests")
diff --git a/tests/py_wheel/py_wheel_tests.bzl b/tests/py_wheel/py_wheel_tests.bzl
new file mode 100644
index 0000000..e580732
--- /dev/null
+++ b/tests/py_wheel/py_wheel_tests.bzl
@@ -0,0 +1,99 @@
+# 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.
+"""Test for py_wheel."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test", "test_suite")
+load("@rules_testing//lib:util.bzl", rt_util = "util")
+load("//python:packaging.bzl", "py_wheel")
+
+_tests = []
+
+def _test_metadata(name):
+ rt_util.helper_target(
+ py_wheel,
+ name = name + "_subject",
+ distribution = "mydist_" + name,
+ version = "0.0.0",
+ )
+ analysis_test(
+ name = name,
+ impl = _test_metadata_impl,
+ target = name + "_subject",
+ )
+
+def _test_metadata_impl(env, target):
+ action = env.expect.that_target(target).action_generating(
+ "{package}/{name}.metadata.txt",
+ )
+ action.content().split("\n").contains_exactly([
+ env.expect.meta.format_str("Name: mydist_{test_name}"),
+ "Metadata-Version: 2.1",
+ "",
+ ])
+
+_tests.append(_test_metadata)
+
+def _test_content_type_from_attr(name):
+ rt_util.helper_target(
+ py_wheel,
+ name = name + "_subject",
+ distribution = "mydist_" + name,
+ version = "0.0.0",
+ description_content_type = "text/x-rst",
+ )
+ analysis_test(
+ name = name,
+ impl = _test_content_type_from_attr_impl,
+ target = name + "_subject",
+ )
+
+def _test_content_type_from_attr_impl(env, target):
+ action = env.expect.that_target(target).action_generating(
+ "{package}/{name}.metadata.txt",
+ )
+ action.content().split("\n").contains(
+ "Description-Content-Type: text/x-rst",
+ )
+
+_tests.append(_test_content_type_from_attr)
+
+def _test_content_type_from_description(name):
+ rt_util.helper_target(
+ py_wheel,
+ name = name + "_subject",
+ distribution = "mydist_" + name,
+ version = "0.0.0",
+ description_file = "desc.md",
+ )
+ analysis_test(
+ name = name,
+ impl = _test_content_type_from_description_impl,
+ target = name + "_subject",
+ )
+
+def _test_content_type_from_description_impl(env, target):
+ action = env.expect.that_target(target).action_generating(
+ "{package}/{name}.metadata.txt",
+ )
+ action.content().split("\n").contains(
+ "Description-Content-Type: text/markdown",
+ )
+
+_tests.append(_test_content_type_from_description)
+
+def py_wheel_test_suite(name):
+ test_suite(
+ name = name,
+ tests = _tests,
+ )
diff --git a/tests/runfiles/BUILD.bazel b/tests/runfiles/BUILD.bazel
new file mode 100644
index 0000000..d62e179
--- /dev/null
+++ b/tests/runfiles/BUILD.bazel
@@ -0,0 +1,7 @@
+load("@rules_python//python:defs.bzl", "py_test")
+
+py_test(
+ name = "runfiles_test",
+ srcs = ["runfiles_test.py"],
+ deps = ["//python/runfiles"],
+)
diff --git a/tests/runfiles/runfiles_test.py b/tests/runfiles/runfiles_test.py
new file mode 100644
index 0000000..3a1f492
--- /dev/null
+++ b/tests/runfiles/runfiles_test.py
@@ -0,0 +1,558 @@
+# pylint: disable=g-bad-file-header
+# 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 tempfile
+import unittest
+
+from python.runfiles import runfiles
+
+
+class RunfilesTest(unittest.TestCase):
+ # """Unit tests for `runfiles.Runfiles`."""
+
+ def testRlocationArgumentValidation(self):
+ r = runfiles.Create({"RUNFILES_DIR": "whatever"})
+ self.assertRaises(ValueError, lambda: r.Rlocation(None))
+ self.assertRaises(ValueError, lambda: r.Rlocation(""))
+ self.assertRaises(TypeError, lambda: r.Rlocation(1))
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("../foo")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("foo/..")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("foo/../bar")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("./foo")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("foo/.")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("foo/./bar")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("//foobar")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("foo//")
+ )
+ self.assertRaisesRegex(
+ ValueError, "is not normalized", lambda: r.Rlocation("foo//bar")
+ )
+ self.assertRaisesRegex(
+ ValueError,
+ "is absolute without a drive letter",
+ lambda: r.Rlocation("\\foo"),
+ )
+
+ def testCreatesManifestBasedRunfiles(self):
+ with _MockFile(contents=["a/b c/d"]) as mf:
+ r = runfiles.Create(
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "RUNFILES_DIR": "ignored when RUNFILES_MANIFEST_FILE has a value",
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ self.assertEqual(r.Rlocation("a/b"), "c/d")
+ self.assertIsNone(r.Rlocation("foo"))
+
+ def testManifestBasedRunfilesEnvVars(self):
+ with _MockFile(name="MANIFEST") as mf:
+ r = runfiles.Create(
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ self.assertDictEqual(
+ r.EnvVars(),
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "RUNFILES_DIR": mf.Path()[: -len("/MANIFEST")],
+ "JAVA_RUNFILES": mf.Path()[: -len("/MANIFEST")],
+ },
+ )
+
+ with _MockFile(name="foo.runfiles_manifest") as mf:
+ r = runfiles.Create(
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ self.assertDictEqual(
+ r.EnvVars(),
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "RUNFILES_DIR": (
+ mf.Path()[: -len("foo.runfiles_manifest")] + "foo.runfiles"
+ ),
+ "JAVA_RUNFILES": (
+ mf.Path()[: -len("foo.runfiles_manifest")] + "foo.runfiles"
+ ),
+ },
+ )
+
+ with _MockFile(name="x_manifest") as mf:
+ r = runfiles.Create(
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ self.assertDictEqual(
+ r.EnvVars(),
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "RUNFILES_DIR": "",
+ "JAVA_RUNFILES": "",
+ },
+ )
+
+ def testCreatesDirectoryBasedRunfiles(self):
+ r = runfiles.Create(
+ {
+ "RUNFILES_DIR": "runfiles/dir",
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ self.assertEqual(r.Rlocation("a/b"), "runfiles/dir/a/b")
+ self.assertEqual(r.Rlocation("foo"), "runfiles/dir/foo")
+
+ def testDirectoryBasedRunfilesEnvVars(self):
+ r = runfiles.Create(
+ {
+ "RUNFILES_DIR": "runfiles/dir",
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ self.assertDictEqual(
+ r.EnvVars(),
+ {
+ "RUNFILES_DIR": "runfiles/dir",
+ "JAVA_RUNFILES": "runfiles/dir",
+ },
+ )
+
+ def testFailsToCreateManifestBasedBecauseManifestDoesNotExist(self):
+ def _Run():
+ runfiles.Create({"RUNFILES_MANIFEST_FILE": "non-existing path"})
+
+ self.assertRaisesRegex(IOError, "non-existing path", _Run)
+
+ def testFailsToCreateAnyRunfilesBecauseEnvvarsAreNotDefined(self):
+ with _MockFile(contents=["a b"]) as mf:
+ runfiles.Create(
+ {
+ "RUNFILES_MANIFEST_FILE": mf.Path(),
+ "RUNFILES_DIR": "whatever",
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ runfiles.Create(
+ {
+ "RUNFILES_DIR": "whatever",
+ "TEST_SRCDIR": "always ignored",
+ }
+ )
+ self.assertIsNone(runfiles.Create({"TEST_SRCDIR": "always ignored"}))
+ self.assertIsNone(runfiles.Create({"FOO": "bar"}))
+
+ def testManifestBasedRlocation(self):
+ with _MockFile(
+ contents=[
+ "Foo/runfile1",
+ "Foo/runfile2 C:/Actual Path\\runfile2",
+ "Foo/Bar/runfile3 D:\\the path\\run file 3.txt",
+ "Foo/Bar/Dir E:\\Actual Path\\Directory",
+ ]
+ ) as mf:
+ r = runfiles.CreateManifestBased(mf.Path())
+ self.assertEqual(r.Rlocation("Foo/runfile1"), "Foo/runfile1")
+ self.assertEqual(r.Rlocation("Foo/runfile2"), "C:/Actual Path\\runfile2")
+ self.assertEqual(
+ r.Rlocation("Foo/Bar/runfile3"), "D:\\the path\\run file 3.txt"
+ )
+ self.assertEqual(
+ r.Rlocation("Foo/Bar/Dir/runfile4"),
+ "E:\\Actual Path\\Directory/runfile4",
+ )
+ self.assertEqual(
+ r.Rlocation("Foo/Bar/Dir/Deeply/Nested/runfile4"),
+ "E:\\Actual Path\\Directory/Deeply/Nested/runfile4",
+ )
+ self.assertIsNone(r.Rlocation("unknown"))
+ if RunfilesTest.IsWindows():
+ self.assertEqual(r.Rlocation("c:/foo"), "c:/foo")
+ self.assertEqual(r.Rlocation("c:\\foo"), "c:\\foo")
+ else:
+ self.assertEqual(r.Rlocation("/foo"), "/foo")
+
+ def testManifestBasedRlocationWithRepoMappingFromMain(self):
+ with _MockFile(
+ contents=[
+ ",config.json,config.json~1.2.3",
+ ",my_module,_main",
+ ",my_protobuf,protobuf~3.19.2",
+ ",my_workspace,_main",
+ "protobuf~3.19.2,config.json,config.json~1.2.3",
+ "protobuf~3.19.2,protobuf,protobuf~3.19.2",
+ ]
+ ) as rm, _MockFile(
+ contents=[
+ "_repo_mapping " + rm.Path(),
+ "config.json /etc/config.json",
+ "protobuf~3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile",
+ "_main/bar/runfile /the/path/./to/other//other runfile.txt",
+ "protobuf~3.19.2/bar/dir E:\\Actual Path\\Directory",
+ ],
+ ) as mf:
+ r = runfiles.CreateManifestBased(mf.Path())
+
+ self.assertEqual(
+ r.Rlocation("my_module/bar/runfile", ""),
+ "/the/path/./to/other//other runfile.txt",
+ )
+ self.assertEqual(
+ r.Rlocation("my_workspace/bar/runfile", ""),
+ "/the/path/./to/other//other runfile.txt",
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/foo/runfile", ""),
+ "C:/Actual Path\\protobuf\\runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/bar/dir", ""), "E:\\Actual Path\\Directory"
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/bar/dir/file", ""),
+ "E:\\Actual Path\\Directory/file",
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le", ""),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi~le",
+ )
+
+ self.assertIsNone(r.Rlocation("protobuf/foo/runfile"))
+ self.assertIsNone(r.Rlocation("protobuf/bar/dir"))
+ self.assertIsNone(r.Rlocation("protobuf/bar/dir/file"))
+ self.assertIsNone(r.Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le"))
+
+ self.assertEqual(
+ r.Rlocation("_main/bar/runfile", ""),
+ "/the/path/./to/other//other runfile.txt",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/foo/runfile", ""),
+ "C:/Actual Path\\protobuf\\runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir", ""), "E:\\Actual Path\\Directory"
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir/file", ""),
+ "E:\\Actual Path\\Directory/file",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", ""),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(r.Rlocation("config.json", ""), "/etc/config.json")
+ self.assertIsNone(r.Rlocation("_main", ""))
+ self.assertIsNone(r.Rlocation("my_module", ""))
+ self.assertIsNone(r.Rlocation("protobuf", ""))
+
+ def testManifestBasedRlocationWithRepoMappingFromOtherRepo(self):
+ with _MockFile(
+ contents=[
+ ",config.json,config.json~1.2.3",
+ ",my_module,_main",
+ ",my_protobuf,protobuf~3.19.2",
+ ",my_workspace,_main",
+ "protobuf~3.19.2,config.json,config.json~1.2.3",
+ "protobuf~3.19.2,protobuf,protobuf~3.19.2",
+ ]
+ ) as rm, _MockFile(
+ contents=[
+ "_repo_mapping " + rm.Path(),
+ "config.json /etc/config.json",
+ "protobuf~3.19.2/foo/runfile C:/Actual Path\\protobuf\\runfile",
+ "_main/bar/runfile /the/path/./to/other//other runfile.txt",
+ "protobuf~3.19.2/bar/dir E:\\Actual Path\\Directory",
+ ],
+ ) as mf:
+ r = runfiles.CreateManifestBased(mf.Path())
+
+ self.assertEqual(
+ r.Rlocation("protobuf/foo/runfile", "protobuf~3.19.2"),
+ "C:/Actual Path\\protobuf\\runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf/bar/dir", "protobuf~3.19.2"),
+ "E:\\Actual Path\\Directory",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf/bar/dir/file", "protobuf~3.19.2"),
+ "E:\\Actual Path\\Directory/file",
+ )
+ self.assertEqual(
+ r.Rlocation(
+ "protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2"
+ ),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi~le",
+ )
+
+ self.assertIsNone(r.Rlocation("my_module/bar/runfile", "protobuf~3.19.2"))
+ self.assertIsNone(r.Rlocation("my_protobuf/foo/runfile", "protobuf~3.19.2"))
+ self.assertIsNone(r.Rlocation("my_protobuf/bar/dir", "protobuf~3.19.2"))
+ self.assertIsNone(
+ r.Rlocation("my_protobuf/bar/dir/file", "protobuf~3.19.2")
+ )
+ self.assertIsNone(
+ r.Rlocation(
+ "my_protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2"
+ )
+ )
+
+ self.assertEqual(
+ r.Rlocation("_main/bar/runfile", "protobuf~3.19.2"),
+ "/the/path/./to/other//other runfile.txt",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/foo/runfile", "protobuf~3.19.2"),
+ "C:/Actual Path\\protobuf\\runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir", "protobuf~3.19.2"),
+ "E:\\Actual Path\\Directory",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir/file", "protobuf~3.19.2"),
+ "E:\\Actual Path\\Directory/file",
+ )
+ self.assertEqual(
+ r.Rlocation(
+ "protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2"
+ ),
+ "E:\\Actual Path\\Directory/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(
+ r.Rlocation("config.json", "protobuf~3.19.2"), "/etc/config.json"
+ )
+ self.assertIsNone(r.Rlocation("_main", "protobuf~3.19.2"))
+ self.assertIsNone(r.Rlocation("my_module", "protobuf~3.19.2"))
+ self.assertIsNone(r.Rlocation("protobuf", "protobuf~3.19.2"))
+
+ def testDirectoryBasedRlocation(self):
+ # The _DirectoryBased strategy simply joins the runfiles directory and the
+ # runfile's path on a "/". This strategy does not perform any normalization,
+ # nor does it check that the path exists.
+ r = runfiles.CreateDirectoryBased("foo/bar baz//qux/")
+ self.assertEqual(r.Rlocation("arg"), "foo/bar baz//qux/arg")
+ if RunfilesTest.IsWindows():
+ self.assertEqual(r.Rlocation("c:/foo"), "c:/foo")
+ self.assertEqual(r.Rlocation("c:\\foo"), "c:\\foo")
+ else:
+ self.assertEqual(r.Rlocation("/foo"), "/foo")
+
+ def testDirectoryBasedRlocationWithRepoMappingFromMain(self):
+ with _MockFile(
+ name="_repo_mapping",
+ contents=[
+ "_,config.json,config.json~1.2.3",
+ ",my_module,_main",
+ ",my_protobuf,protobuf~3.19.2",
+ ",my_workspace,_main",
+ "protobuf~3.19.2,config.json,config.json~1.2.3",
+ "protobuf~3.19.2,protobuf,protobuf~3.19.2",
+ ],
+ ) as rm:
+ dir = os.path.dirname(rm.Path())
+ r = runfiles.CreateDirectoryBased(dir)
+
+ self.assertEqual(
+ r.Rlocation("my_module/bar/runfile", ""), dir + "/_main/bar/runfile"
+ )
+ self.assertEqual(
+ r.Rlocation("my_workspace/bar/runfile", ""), dir + "/_main/bar/runfile"
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/foo/runfile", ""),
+ dir + "/protobuf~3.19.2/foo/runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/bar/dir", ""), dir + "/protobuf~3.19.2/bar/dir"
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/bar/dir/file", ""),
+ dir + "/protobuf~3.19.2/bar/dir/file",
+ )
+ self.assertEqual(
+ r.Rlocation("my_protobuf/bar/dir/de eply/nes ted/fi~le", ""),
+ dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(
+ r.Rlocation("protobuf/foo/runfile", ""), dir + "/protobuf/foo/runfile"
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf/bar/dir/dir/de eply/nes ted/fi~le", ""),
+ dir + "/protobuf/bar/dir/dir/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(
+ r.Rlocation("_main/bar/runfile", ""), dir + "/_main/bar/runfile"
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/foo/runfile", ""),
+ dir + "/protobuf~3.19.2/foo/runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir", ""),
+ dir + "/protobuf~3.19.2/bar/dir",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir/file", ""),
+ dir + "/protobuf~3.19.2/bar/dir/file",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", ""),
+ dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(r.Rlocation("config.json", ""), dir + "/config.json")
+
+ def testDirectoryBasedRlocationWithRepoMappingFromOtherRepo(self):
+ with _MockFile(
+ name="_repo_mapping",
+ contents=[
+ "_,config.json,config.json~1.2.3",
+ ",my_module,_main",
+ ",my_protobuf,protobuf~3.19.2",
+ ",my_workspace,_main",
+ "protobuf~3.19.2,config.json,config.json~1.2.3",
+ "protobuf~3.19.2,protobuf,protobuf~3.19.2",
+ ],
+ ) as rm:
+ dir = os.path.dirname(rm.Path())
+ r = runfiles.CreateDirectoryBased(dir)
+
+ self.assertEqual(
+ r.Rlocation("protobuf/foo/runfile", "protobuf~3.19.2"),
+ dir + "/protobuf~3.19.2/foo/runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf/bar/dir", "protobuf~3.19.2"),
+ dir + "/protobuf~3.19.2/bar/dir",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf/bar/dir/file", "protobuf~3.19.2"),
+ dir + "/protobuf~3.19.2/bar/dir/file",
+ )
+ self.assertEqual(
+ r.Rlocation(
+ "protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2"
+ ),
+ dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(
+ r.Rlocation("my_module/bar/runfile", "protobuf~3.19.2"),
+ dir + "/my_module/bar/runfile",
+ )
+ self.assertEqual(
+ r.Rlocation(
+ "my_protobuf/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2"
+ ),
+ dir + "/my_protobuf/bar/dir/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(
+ r.Rlocation("_main/bar/runfile", "protobuf~3.19.2"),
+ dir + "/_main/bar/runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/foo/runfile", "protobuf~3.19.2"),
+ dir + "/protobuf~3.19.2/foo/runfile",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir", "protobuf~3.19.2"),
+ dir + "/protobuf~3.19.2/bar/dir",
+ )
+ self.assertEqual(
+ r.Rlocation("protobuf~3.19.2/bar/dir/file", "protobuf~3.19.2"),
+ dir + "/protobuf~3.19.2/bar/dir/file",
+ )
+ self.assertEqual(
+ r.Rlocation(
+ "protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le", "protobuf~3.19.2"
+ ),
+ dir + "/protobuf~3.19.2/bar/dir/de eply/nes ted/fi~le",
+ )
+
+ self.assertEqual(
+ r.Rlocation("config.json", "protobuf~3.19.2"), dir + "/config.json"
+ )
+
+ def testCurrentRepository(self):
+ # This test assumes that it is running without --enable_bzlmod as the
+ # correct result with Bzlmod would be the empty string - the canonical
+ # name # of the main repository. Without Bzlmod, the main repository is
+ # treated just like any other repository and has the name of its
+ # runfiles directory returned, which coincides with the name specified
+ # in the WORKSPACE file.
+ #
+ # Specify a fake runfiles directory to verify that its value isn't used
+ # by the function.
+ self.assertEqual(
+ runfiles.Create({"RUNFILES_DIR": "whatever"}).CurrentRepository(),
+ "rules_python",
+ )
+
+ @staticmethod
+ def IsWindows():
+ return os.name == "nt"
+
+
+class _MockFile(object):
+ def __init__(self, name=None, contents=None):
+ self._contents = contents or []
+ self._name = name or "x"
+ self._path = None
+
+ def __enter__(self):
+ tmpdir = os.environ.get("TEST_TMPDIR")
+ self._path = os.path.join(tempfile.mkdtemp(dir=tmpdir), self._name)
+ with open(self._path, "wt") as f:
+ f.writelines(l + "\n" for l in self._contents)
+ return self
+
+ def __exit__(self, exc_type, exc_value, traceback):
+ os.remove(self._path)
+ os.rmdir(os.path.dirname(self._path))
+
+ def Path(self):
+ return self._path
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/runfiles/runfiles_wheel_integration_test.sh b/tests/runfiles/runfiles_wheel_integration_test.sh
new file mode 100755
index 0000000..8e9c608
--- /dev/null
+++ b/tests/runfiles/runfiles_wheel_integration_test.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# 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.
+
+# Manual test, run outside of Bazel, to check that our runfiles wheel should be functional
+# for users who install it from pypi.
+set -o errexit
+
+SCRIPTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )"
+
+bazel 2>/dev/null build --stamp --embed_label=1.2.3 //python/runfiles:wheel
+wheelpath=$SCRIPTPATH/../../$(bazel 2>/dev/null cquery --output=files //python/runfiles:wheel)
+PYTHONPATH=$wheelpath python3 -c 'import importlib;print(importlib.import_module("runfiles"))'
diff --git a/tests/version_label/BUILD.bazel b/tests/version_label/BUILD.bazel
new file mode 100644
index 0000000..1dcfece
--- /dev/null
+++ b/tests/version_label/BUILD.bazel
@@ -0,0 +1,17 @@
+# 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.
+
+load(":version_label_test.bzl", "version_label_test_suite")
+
+version_label_test_suite(name = "version_label_tests")
diff --git a/tests/version_label/version_label_test.bzl b/tests/version_label/version_label_test.bzl
new file mode 100644
index 0000000..b4ed6f9
--- /dev/null
+++ b/tests/version_label/version_label_test.bzl
@@ -0,0 +1,52 @@
+# 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.
+
+""
+
+load("@rules_testing//lib:test_suite.bzl", "test_suite")
+load("//python/private:version_label.bzl", "version_label") # buildifier: disable=bzl-visibility
+
+_tests = []
+
+def _test_version_label_from_major_minor_version(env):
+ actual = version_label("3.9")
+ env.expect.that_str(actual).equals("39")
+
+_tests.append(_test_version_label_from_major_minor_version)
+
+def _test_version_label_from_major_minor_patch_version(env):
+ actual = version_label("3.9.3")
+ env.expect.that_str(actual).equals("39")
+
+_tests.append(_test_version_label_from_major_minor_patch_version)
+
+def _test_version_label_from_major_minor_version_custom_sep(env):
+ actual = version_label("3.9", sep = "_")
+ env.expect.that_str(actual).equals("3_9")
+
+_tests.append(_test_version_label_from_major_minor_version_custom_sep)
+
+def _test_version_label_from_complex_version(env):
+ actual = version_label("3.9.3-rc.0")
+ env.expect.that_str(actual).equals("39")
+
+_tests.append(_test_version_label_from_complex_version)
+
+def version_label_test_suite(name):
+ """Create the test suite.
+
+ Args:
+ name: the name of the test suite
+ """
+ test_suite(name = name, basic_tests = _tests)
diff --git a/tools/BUILD.bazel b/tools/BUILD.bazel
new file mode 100644
index 0000000..fd951d9
--- /dev/null
+++ b/tools/BUILD.bazel
@@ -0,0 +1,33 @@
+# Copyright 2017 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_binary")
+
+package(default_visibility = ["//visibility:public"])
+
+licenses(["notice"])
+
+# Implementation detail of py_wheel rule.
+py_binary(
+ name = "wheelmaker",
+ srcs = ["wheelmaker.py"],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = [
+ "BUILD.bazel",
+ "wheelmaker.py",
+ ],
+ visibility = ["//:__pkg__"],
+)
diff --git a/tools/bazel_integration_test/BUILD.bazel b/tools/bazel_integration_test/BUILD.bazel
new file mode 100644
index 0000000..10566c4
--- /dev/null
+++ b/tools/bazel_integration_test/BUILD.bazel
@@ -0,0 +1 @@
+exports_files(["test_runner.py"])
diff --git a/tools/bazel_integration_test/bazel_integration_test.bzl b/tools/bazel_integration_test/bazel_integration_test.bzl
new file mode 100644
index 0000000..c016551
--- /dev/null
+++ b/tools/bazel_integration_test/bazel_integration_test.bzl
@@ -0,0 +1,134 @@
+# 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.
+
+"Define a rule for running bazel test under Bazel"
+
+load("//:version.bzl", "SUPPORTED_BAZEL_VERSIONS", "bazel_version_to_binary_label")
+load("//python:defs.bzl", "py_test")
+
+BAZEL_BINARY = bazel_version_to_binary_label(SUPPORTED_BAZEL_VERSIONS[0])
+
+_ATTRS = {
+ "bazel_binary": attr.label(
+ default = BAZEL_BINARY,
+ doc = """The bazel binary files to test against.
+
+It is assumed by the test runner that the bazel binary is found at label_workspace/bazel (wksp/bazel.exe on Windows)""",
+ ),
+ "bazel_commands": attr.string_list(
+ default = ["info", "test --test_output=errors ..."],
+ doc = """The list of bazel commands to run.
+
+Note that if a command contains a bare `--` argument, the --test_arg passed to Bazel will appear before it.
+""",
+ ),
+ "bzlmod": attr.bool(
+ default = False,
+ doc = """Whether the test uses bzlmod.""",
+ ),
+ "workspace_files": attr.label(
+ doc = """A filegroup of all files in the workspace-under-test necessary to run the test.""",
+ ),
+}
+
+def _config_impl(ctx):
+ if len(SUPPORTED_BAZEL_VERSIONS) > 1:
+ fail("""
+ bazel_integration_test doesn't support multiple Bazel versions to test against yet.
+ """)
+ if len(ctx.files.workspace_files) == 0:
+ fail("""
+No files were found to run under integration testing. See comment in /.bazelrc.
+You probably need to run
+ tools/bazel_integration_test/update_deleted_packages.sh
+""")
+
+ # Serialize configuration file for test runner
+ config = ctx.actions.declare_file("%s.json" % ctx.attr.name)
+ ctx.actions.write(
+ output = config,
+ content = """
+{{
+ "workspaceRoot": "{TMPL_workspace_root}",
+ "bazelBinaryWorkspace": "{TMPL_bazel_binary_workspace}",
+ "bazelCommands": [ {TMPL_bazel_commands} ],
+ "bzlmod": {TMPL_bzlmod}
+}}
+""".format(
+ TMPL_workspace_root = ctx.files.workspace_files[0].dirname,
+ TMPL_bazel_binary_workspace = ctx.attr.bazel_binary.label.workspace_name,
+ TMPL_bazel_commands = ", ".join(["\"%s\"" % s for s in ctx.attr.bazel_commands]),
+ TMPL_bzlmod = str(ctx.attr.bzlmod).lower(),
+ ),
+ )
+
+ return [DefaultInfo(
+ files = depset([config]),
+ runfiles = ctx.runfiles(files = [config]),
+ )]
+
+_config = rule(
+ implementation = _config_impl,
+ doc = "Configures an integration test that runs a specified version of bazel against an external workspace.",
+ attrs = _ATTRS,
+)
+
+def bazel_integration_test(name, override_bazel_version = None, bzlmod = False, dirname = None, **kwargs):
+ """Wrapper macro to set default srcs and run a py_test with config
+
+ Args:
+ name: name of the resulting py_test
+ override_bazel_version: bazel version to use in test
+ bzlmod: whether the test uses bzlmod
+ dirname: the directory name of the test. Defaults to value of `name` after trimming the `_example` suffix.
+ **kwargs: additional attributes like timeout and visibility
+ """
+
+ # By default, we assume sources for "pip_example" are in examples/pip/**/*
+ dirname = dirname or name[:-len("_example")]
+ native.filegroup(
+ name = "_%s_sources" % name,
+ srcs = native.glob(
+ ["%s/**/*" % dirname],
+ exclude = ["%s/bazel-*/**" % dirname],
+ ),
+ )
+ workspace_files = kwargs.pop("workspace_files", "_%s_sources" % name)
+
+ bazel_binary = BAZEL_BINARY if not override_bazel_version else bazel_version_to_binary_label(override_bazel_version)
+ _config(
+ name = "_%s_config" % name,
+ workspace_files = workspace_files,
+ bazel_binary = bazel_binary,
+ bzlmod = bzlmod,
+ )
+
+ tags = kwargs.pop("tags", [])
+ tags.append("integration-test")
+
+ py_test(
+ name = name,
+ srcs = [Label("//tools/bazel_integration_test:test_runner.py")],
+ main = "test_runner.py",
+ args = [native.package_name() + "/_%s_config.json" % name],
+ deps = [Label("//python/runfiles")],
+ data = [
+ bazel_binary,
+ "//:distribution",
+ "_%s_config" % name,
+ workspace_files,
+ ],
+ tags = tags,
+ **kwargs
+ )
diff --git a/tools/bazel_integration_test/test_runner.py b/tools/bazel_integration_test/test_runner.py
new file mode 100644
index 0000000..3940e87
--- /dev/null
+++ b/tools/bazel_integration_test/test_runner.py
@@ -0,0 +1,111 @@
+# 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.
+
+import json
+import os
+import platform
+import re
+import shutil
+import sys
+import tempfile
+import textwrap
+from pathlib import Path
+from subprocess import Popen
+
+from rules_python.python.runfiles import runfiles
+
+r = runfiles.Create()
+
+
+def main(conf_file):
+ with open(conf_file) as j:
+ config = json.load(j)
+
+ isWindows = platform.system() == "Windows"
+ bazelBinary = r.Rlocation(
+ os.path.join(
+ config["bazelBinaryWorkspace"], "bazel.exe" if isWindows else "bazel"
+ )
+ )
+
+ workspacePath = config["workspaceRoot"]
+ # Canonicalize bazel external/some_repo/foo
+ if workspacePath.startswith("external/"):
+ workspacePath = ".." + workspacePath[len("external") :]
+
+ with tempfile.TemporaryDirectory(dir=os.environ["TEST_TMPDIR"]) as tmp_homedir:
+ home_bazel_rc = Path(tmp_homedir) / ".bazelrc"
+ home_bazel_rc.write_text(
+ textwrap.dedent(
+ """\
+ startup --max_idle_secs=1
+ common --announce_rc
+ """
+ )
+ )
+
+ with tempfile.TemporaryDirectory(dir=os.environ["TEST_TMPDIR"]) as tmpdir:
+ workdir = os.path.join(tmpdir, "wksp")
+ print("copying workspace under test %s to %s" % (workspacePath, workdir))
+ shutil.copytree(workspacePath, workdir)
+
+ for command in config["bazelCommands"]:
+ bazel_args = command.split(" ")
+ bazel_args.append(
+ "--override_repository=rules_python=%s/rules_python"
+ % os.environ["TEST_SRCDIR"]
+ )
+ bazel_args.append(
+ "--override_repository=rules_python_gazelle_plugin=%s/rules_python_gazelle_plugin"
+ % os.environ["TEST_SRCDIR"]
+ )
+
+ # TODO: --override_module isn't supported in the current BAZEL_VERSION (5.2.0)
+ # This condition and attribute can be removed when bazel is updated for
+ # the rest of rules_python.
+ if config["bzlmod"]:
+ bazel_args.append(
+ "--override_module=rules_python=%s/rules_python"
+ % os.environ["TEST_SRCDIR"]
+ )
+ bazel_args.append("--enable_bzlmod")
+
+ # Bazel's wrapper script needs this or you get
+ # 2020/07/13 21:58:11 could not get the user's cache directory: $HOME is not defined
+ os.environ["HOME"] = str(tmp_homedir)
+
+ bazel_args.insert(0, bazelBinary)
+ bazel_process = Popen(bazel_args, cwd=workdir)
+ bazel_process.wait()
+ error = bazel_process.returncode != 0
+
+ if platform.system() == "Windows":
+ # Cleanup any bazel files
+ bazel_process = Popen([bazelBinary, "clean"], cwd=workdir)
+ bazel_process.wait()
+ error |= bazel_process.returncode != 0
+
+ # Shutdown the bazel instance to avoid issues cleaning up the workspace
+ bazel_process = Popen([bazelBinary, "shutdown"], cwd=workdir)
+ bazel_process.wait()
+ error |= bazel_process.returncode != 0
+
+ if error != 0:
+ # Test failure in Bazel is exit 3
+ # https://github.com/bazelbuild/bazel/blob/486206012a664ecb20bdb196a681efc9a9825049/src/main/java/com/google/devtools/build/lib/util/ExitCode.java#L44
+ sys.exit(3)
+
+
+if __name__ == "__main__":
+ main(sys.argv[1])
diff --git a/tools/bazel_integration_test/update_deleted_packages.sh b/tools/bazel_integration_test/update_deleted_packages.sh
new file mode 100755
index 0000000..54db026
--- /dev/null
+++ b/tools/bazel_integration_test/update_deleted_packages.sh
@@ -0,0 +1,39 @@
+#!/usr/bin/env bash
+# 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.
+
+# For integration tests, we want to be able to glob() up the sources inside a nested package
+# See explanation in .bazelrc
+#
+# This script ensures that we only delete subtrees that have something a file
+# signifying a new bazel workspace, whether it be bzlmod or classic. Generic
+# algorithm:
+# 1. Get all directories where a WORKSPACE or MODULE.bazel exists.
+# 2. For each of the directories, get all directories that contains a BUILD.bazel file.
+# 3. Sort and remove duplicates.
+
+set -euxo pipefail
+
+DIR="$(dirname $0)/../.."
+cd $DIR
+
+# The sed -i.bak pattern is compatible between macos and linux
+sed -i.bak "/^[^#].*--deleted_packages/s#=.*#=$(\
+ find examples/*/* tests/*/* \( -name WORKSPACE -or -name MODULE.bazel \) |
+ xargs -n 1 dirname |
+ xargs -n 1 -I{} find {} \( -name BUILD -or -name BUILD.bazel \) |
+ xargs -n 1 dirname |
+ sort -u |
+ paste -sd, -\
+)#" $DIR/.bazelrc && rm .bazelrc.bak
diff --git a/tools/build_defs/python/BUILD.bazel b/tools/build_defs/python/BUILD.bazel
new file mode 100644
index 0000000..aa21042
--- /dev/null
+++ b/tools/build_defs/python/BUILD.bazel
@@ -0,0 +1,13 @@
+# 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.
diff --git a/tools/build_defs/python/tests/BUILD.bazel b/tools/build_defs/python/tests/BUILD.bazel
new file mode 100644
index 0000000..e271850
--- /dev/null
+++ b/tools/build_defs/python/tests/BUILD.bazel
@@ -0,0 +1,27 @@
+# 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.
+
+platform(
+ name = "mac",
+ constraint_values = [
+ "@platforms//os:macos",
+ ],
+)
+
+platform(
+ name = "linux",
+ constraint_values = [
+ "@platforms//os:linux",
+ ],
+)
diff --git a/tools/build_defs/python/tests/base_tests.bzl b/tools/build_defs/python/tests/base_tests.bzl
new file mode 100644
index 0000000..467611f
--- /dev/null
+++ b/tools/build_defs/python/tests/base_tests.bzl
@@ -0,0 +1,124 @@
+# 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.
+"""Tests common to py_test, py_binary, and py_library rules."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
+load("@rules_testing//lib:truth.bzl", "matching")
+load("@rules_testing//lib:util.bzl", "PREVENT_IMPLICIT_BUILDING_TAGS", rt_util = "util")
+load("//python:defs.bzl", "PyInfo")
+load("//tools/build_defs/python/tests:py_info_subject.bzl", "py_info_subject")
+load("//tools/build_defs/python/tests:util.bzl", pt_util = "util")
+
+_tests = []
+
+def _produces_py_info_impl(ctx):
+ return [PyInfo(transitive_sources = depset(ctx.files.srcs))]
+
+_produces_py_info = rule(
+ implementation = _produces_py_info_impl,
+ attrs = {"srcs": attr.label_list(allow_files = True)},
+)
+
+def _test_consumes_provider(name, config):
+ rt_util.helper_target(
+ config.base_test_rule,
+ name = name + "_subject",
+ deps = [name + "_produces_py_info"],
+ )
+ rt_util.helper_target(
+ _produces_py_info,
+ name = name + "_produces_py_info",
+ srcs = [rt_util.empty_file(name + "_produce.py")],
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_consumes_provider_impl,
+ )
+
+def _test_consumes_provider_impl(env, target):
+ env.expect.that_target(target).provider(
+ PyInfo,
+ factory = py_info_subject,
+ ).transitive_sources().contains("{package}/{test_name}_produce.py")
+
+_tests.append(_test_consumes_provider)
+
+def _test_requires_provider(name, config):
+ rt_util.helper_target(
+ config.base_test_rule,
+ name = name + "_subject",
+ deps = [name + "_nopyinfo"],
+ )
+ rt_util.helper_target(
+ native.filegroup,
+ name = name + "_nopyinfo",
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_requires_provider_impl,
+ expect_failure = True,
+ )
+
+def _test_requires_provider_impl(env, target):
+ env.expect.that_target(target).failures().contains_predicate(
+ matching.str_matches("mandatory*PyInfo"),
+ )
+
+_tests.append(_test_requires_provider)
+
+def _test_data_sets_uses_shared_library(name, config):
+ rt_util.helper_target(
+ config.base_test_rule,
+ name = name + "_subject",
+ data = [rt_util.empty_file(name + "_dso.so")],
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_data_sets_uses_shared_library_impl,
+ )
+
+def _test_data_sets_uses_shared_library_impl(env, target):
+ env.expect.that_target(target).provider(
+ PyInfo,
+ factory = py_info_subject,
+ ).uses_shared_libraries().equals(True)
+
+_tests.append(_test_data_sets_uses_shared_library)
+
+def _test_tags_can_be_tuple(name, config):
+ # We don't use a helper because we want to ensure that value passed is
+ # a tuple.
+ config.base_test_rule(
+ name = name + "_subject",
+ tags = ("one", "two") + tuple(PREVENT_IMPLICIT_BUILDING_TAGS),
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_tags_can_be_tuple_impl,
+ )
+
+def _test_tags_can_be_tuple_impl(env, target):
+ env.expect.that_target(target).tags().contains_at_least([
+ "one",
+ "two",
+ ])
+
+_tests.append(_test_tags_can_be_tuple)
+
+def create_base_tests(config):
+ return pt_util.create_tests(_tests, config = config)
diff --git a/tools/build_defs/python/tests/py_binary/BUILD.bazel b/tools/build_defs/python/tests/py_binary/BUILD.bazel
new file mode 100644
index 0000000..17a6690
--- /dev/null
+++ b/tools/build_defs/python/tests/py_binary/BUILD.bazel
@@ -0,0 +1,17 @@
+# 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.
+
+load(":py_binary_tests.bzl", "py_binary_test_suite")
+
+py_binary_test_suite(name = "py_binary_tests")
diff --git a/tools/build_defs/python/tests/py_binary/py_binary_tests.bzl b/tools/build_defs/python/tests/py_binary/py_binary_tests.bzl
new file mode 100644
index 0000000..8d32632
--- /dev/null
+++ b/tools/build_defs/python/tests/py_binary/py_binary_tests.bzl
@@ -0,0 +1,28 @@
+# 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.
+"""Tests for py_binary."""
+
+load("//python:defs.bzl", "py_binary")
+load(
+ "//tools/build_defs/python/tests:py_executable_base_tests.bzl",
+ "create_executable_tests",
+)
+
+def py_binary_test_suite(name):
+ config = struct(rule = py_binary)
+
+ native.test_suite(
+ name = name,
+ tests = create_executable_tests(config),
+ )
diff --git a/tools/build_defs/python/tests/py_executable_base_tests.bzl b/tools/build_defs/python/tests/py_executable_base_tests.bzl
new file mode 100644
index 0000000..c66ea11
--- /dev/null
+++ b/tools/build_defs/python/tests/py_executable_base_tests.bzl
@@ -0,0 +1,272 @@
+# 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.
+"""Tests common to py_binary and py_test (executable rules)."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
+load("@rules_testing//lib:truth.bzl", "matching")
+load("@rules_testing//lib:util.bzl", rt_util = "util")
+load("//tools/build_defs/python/tests:base_tests.bzl", "create_base_tests")
+load("//tools/build_defs/python/tests:util.bzl", "WINDOWS_ATTR", pt_util = "util")
+
+_tests = []
+
+def _test_executable_in_runfiles(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = [name + "_subject.py"],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_executable_in_runfiles_impl,
+ target = name + "_subject",
+ attrs = WINDOWS_ATTR,
+ )
+
+_tests.append(_test_executable_in_runfiles)
+
+def _test_executable_in_runfiles_impl(env, target):
+ if pt_util.is_windows(env):
+ exe = ".exe"
+ else:
+ exe = ""
+
+ env.expect.that_target(target).runfiles().contains_at_least([
+ "{workspace}/{package}/{test_name}_subject" + exe,
+ ])
+
+def _test_default_main_can_be_generated(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = [rt_util.empty_file(name + "_subject.py")],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_default_main_can_be_generated_impl,
+ target = name + "_subject",
+ )
+
+_tests.append(_test_default_main_can_be_generated)
+
+def _test_default_main_can_be_generated_impl(env, target):
+ env.expect.that_target(target).default_outputs().contains(
+ "{package}/{test_name}_subject.py",
+ )
+
+def _test_default_main_can_have_multiple_path_segments(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "/subject",
+ srcs = [name + "/subject.py"],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_default_main_can_have_multiple_path_segments_impl,
+ target = name + "/subject",
+ )
+
+_tests.append(_test_default_main_can_have_multiple_path_segments)
+
+def _test_default_main_can_have_multiple_path_segments_impl(env, target):
+ env.expect.that_target(target).default_outputs().contains(
+ "{package}/{test_name}/subject.py",
+ )
+
+def _test_default_main_must_be_in_srcs(name, config):
+ # Bazel 5 will crash with a Java stacktrace when the native Python
+ # rules have an error.
+ if not pt_util.is_bazel_6_or_higher():
+ rt_util.skip_test(name = name)
+ return
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = ["other.py"],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_default_main_must_be_in_srcs_impl,
+ target = name + "_subject",
+ expect_failure = True,
+ )
+
+_tests.append(_test_default_main_must_be_in_srcs)
+
+def _test_default_main_must_be_in_srcs_impl(env, target):
+ env.expect.that_target(target).failures().contains_predicate(
+ matching.str_matches("default*does not appear in srcs"),
+ )
+
+def _test_default_main_cannot_be_ambiguous(name, config):
+ # Bazel 5 will crash with a Java stacktrace when the native Python
+ # rules have an error.
+ if not pt_util.is_bazel_6_or_higher():
+ rt_util.skip_test(name = name)
+ return
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = [name + "_subject.py", "other/{}_subject.py".format(name)],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_default_main_cannot_be_ambiguous_impl,
+ target = name + "_subject",
+ expect_failure = True,
+ )
+
+_tests.append(_test_default_main_cannot_be_ambiguous)
+
+def _test_default_main_cannot_be_ambiguous_impl(env, target):
+ env.expect.that_target(target).failures().contains_predicate(
+ matching.str_matches("default main*matches multiple files"),
+ )
+
+def _test_explicit_main(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = ["custom.py"],
+ main = "custom.py",
+ )
+ analysis_test(
+ name = name,
+ impl = _test_explicit_main_impl,
+ target = name + "_subject",
+ )
+
+_tests.append(_test_explicit_main)
+
+def _test_explicit_main_impl(env, target):
+ # There isn't a direct way to ask what main file was selected, so we
+ # rely on it being in the default outputs.
+ env.expect.that_target(target).default_outputs().contains(
+ "{package}/custom.py",
+ )
+
+def _test_explicit_main_cannot_be_ambiguous(name, config):
+ # Bazel 5 will crash with a Java stacktrace when the native Python
+ # rules have an error.
+ if not pt_util.is_bazel_6_or_higher():
+ rt_util.skip_test(name = name)
+ return
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = ["x/foo.py", "y/foo.py"],
+ main = "foo.py",
+ )
+ analysis_test(
+ name = name,
+ impl = _test_explicit_main_cannot_be_ambiguous_impl,
+ target = name + "_subject",
+ expect_failure = True,
+ )
+
+_tests.append(_test_explicit_main_cannot_be_ambiguous)
+
+def _test_explicit_main_cannot_be_ambiguous_impl(env, target):
+ env.expect.that_target(target).failures().contains_predicate(
+ matching.str_matches("foo.py*matches multiple"),
+ )
+
+def _test_files_to_build(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = [name + "_subject.py"],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_files_to_build_impl,
+ target = name + "_subject",
+ attrs = WINDOWS_ATTR,
+ )
+
+_tests.append(_test_files_to_build)
+
+def _test_files_to_build_impl(env, target):
+ default_outputs = env.expect.that_target(target).default_outputs()
+ if pt_util.is_windows(env):
+ default_outputs.contains("{package}/{test_name}_subject.exe")
+ else:
+ default_outputs.contains_exactly([
+ "{package}/{test_name}_subject",
+ "{package}/{test_name}_subject.py",
+ ])
+
+def _test_name_cannot_end_in_py(name, config):
+ # Bazel 5 will crash with a Java stacktrace when the native Python
+ # rules have an error.
+ if not pt_util.is_bazel_6_or_higher():
+ rt_util.skip_test(name = name)
+ return
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject.py",
+ srcs = ["main.py"],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_name_cannot_end_in_py_impl,
+ target = name + "_subject.py",
+ expect_failure = True,
+ )
+
+_tests.append(_test_name_cannot_end_in_py)
+
+def _test_name_cannot_end_in_py_impl(env, target):
+ env.expect.that_target(target).failures().contains_predicate(
+ matching.str_matches("name must not end in*.py"),
+ )
+
+# Can't test this -- mandatory validation happens before analysis test
+# can intercept it
+# TODO(#1069): Once re-implemented in Starlark, modify rule logic to make this
+# testable.
+# def _test_srcs_is_mandatory(name, config):
+# rt_util.helper_target(
+# config.rule,
+# name = name + "_subject",
+# )
+# analysis_test(
+# name = name,
+# impl = _test_srcs_is_mandatory,
+# target = name + "_subject",
+# expect_failure = True,
+# )
+#
+# _tests.append(_test_srcs_is_mandatory)
+#
+# def _test_srcs_is_mandatory_impl(env, target):
+# env.expect.that_target(target).failures().contains_predicate(
+# matching.str_matches("mandatory*srcs"),
+# )
+
+# =====
+# You were gonna add a test at the end, weren't you?
+# Nope. Please keep them sorted; put it in its alphabetical location.
+# Here's the alphabet so you don't have to sing that song in your head:
+# A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+# =====
+
+def create_executable_tests(config):
+ def _executable_with_srcs_wrapper(name, **kwargs):
+ if not kwargs.get("srcs"):
+ kwargs["srcs"] = [name + ".py"]
+ config.rule(name = name, **kwargs)
+
+ config = pt_util.struct_with(config, base_test_rule = _executable_with_srcs_wrapper)
+ return pt_util.create_tests(_tests, config = config) + create_base_tests(config = config)
diff --git a/tools/build_defs/python/tests/py_info_subject.bzl b/tools/build_defs/python/tests/py_info_subject.bzl
new file mode 100644
index 0000000..20185e5
--- /dev/null
+++ b/tools/build_defs/python/tests/py_info_subject.bzl
@@ -0,0 +1,95 @@
+# 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.
+"""PyInfo testing subject."""
+
+load("@rules_testing//lib:truth.bzl", "subjects")
+
+def py_info_subject(info, *, meta):
+ """Creates a new `PyInfoSubject` for a PyInfo provider instance.
+
+ Method: PyInfoSubject.new
+
+ Args:
+ info: The PyInfo object
+ meta: ExpectMeta object.
+
+ Returns:
+ A `PyInfoSubject` struct
+ """
+
+ # buildifier: disable=uninitialized
+ public = struct(
+ # go/keep-sorted start
+ has_py2_only_sources = lambda *a, **k: _py_info_subject_has_py2_only_sources(self, *a, **k),
+ has_py3_only_sources = lambda *a, **k: _py_info_subject_has_py3_only_sources(self, *a, **k),
+ imports = lambda *a, **k: _py_info_subject_imports(self, *a, **k),
+ transitive_sources = lambda *a, **k: _py_info_subject_transitive_sources(self, *a, **k),
+ uses_shared_libraries = lambda *a, **k: _py_info_subject_uses_shared_libraries(self, *a, **k),
+ # go/keep-sorted end
+ )
+ self = struct(
+ actual = info,
+ meta = meta,
+ )
+ return public
+
+def _py_info_subject_has_py2_only_sources(self):
+ """Returns a `BoolSubject` for the `has_py2_only_sources` attribute.
+
+ Method: PyInfoSubject.has_py2_only_sources
+ """
+ return subjects.bool(
+ self.actual.has_py2_only_sources,
+ meta = self.meta.derive("has_py2_only_sources()"),
+ )
+
+def _py_info_subject_has_py3_only_sources(self):
+ """Returns a `BoolSubject` for the `has_py3_only_sources` attribute.
+
+ Method: PyInfoSubject.has_py3_only_sources
+ """
+ return subjects.bool(
+ self.actual.has_py3_only_sources,
+ meta = self.meta.derive("has_py3_only_sources()"),
+ )
+
+def _py_info_subject_imports(self):
+ """Returns a `CollectionSubject` for the `imports` attribute.
+
+ Method: PyInfoSubject.imports
+ """
+ return subjects.collection(
+ self.actual.imports,
+ meta = self.meta.derive("imports()"),
+ )
+
+def _py_info_subject_transitive_sources(self):
+ """Returns a `DepsetFileSubject` for the `transitive_sources` attribute.
+
+ Method: PyInfoSubject.transitive_sources
+ """
+ return subjects.depset_file(
+ self.actual.transitive_sources,
+ meta = self.meta.derive("transitive_sources()"),
+ )
+
+def _py_info_subject_uses_shared_libraries(self):
+ """Returns a `BoolSubject` for the `uses_shared_libraries` attribute.
+
+ Method: PyInfoSubject.uses_shared_libraries
+ """
+ return subjects.bool(
+ self.actual.uses_shared_libraries,
+ meta = self.meta.derive("uses_shared_libraries()"),
+ )
diff --git a/tools/build_defs/python/tests/py_library/BUILD.bazel b/tools/build_defs/python/tests/py_library/BUILD.bazel
new file mode 100644
index 0000000..9de414b
--- /dev/null
+++ b/tools/build_defs/python/tests/py_library/BUILD.bazel
@@ -0,0 +1,18 @@
+# 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.
+"""Tests for py_library."""
+
+load(":py_library_tests.bzl", "py_library_test_suite")
+
+py_library_test_suite(name = "py_library_tests")
diff --git a/tools/build_defs/python/tests/py_library/py_library_tests.bzl b/tools/build_defs/python/tests/py_library/py_library_tests.bzl
new file mode 100644
index 0000000..1fcb0c1
--- /dev/null
+++ b/tools/build_defs/python/tests/py_library/py_library_tests.bzl
@@ -0,0 +1,148 @@
+"""Test for py_library."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
+load("@rules_testing//lib:truth.bzl", "matching")
+load("@rules_testing//lib:util.bzl", rt_util = "util")
+load("//python:defs.bzl", "PyRuntimeInfo", "py_library")
+load("//tools/build_defs/python/tests:base_tests.bzl", "create_base_tests")
+load("//tools/build_defs/python/tests:util.bzl", pt_util = "util")
+
+_tests = []
+
+def _test_py_runtime_info_not_present(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = ["lib.py"],
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_py_runtime_info_not_present_impl,
+ )
+
+def _test_py_runtime_info_not_present_impl(env, target):
+ env.expect.that_bool(PyRuntimeInfo in target).equals(False)
+
+_tests.append(_test_py_runtime_info_not_present)
+
+def _test_files_to_build(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = ["lib.py"],
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_files_to_build_impl,
+ )
+
+def _test_files_to_build_impl(env, target):
+ env.expect.that_target(target).default_outputs().contains_exactly([
+ "{package}/lib.py",
+ ])
+
+_tests.append(_test_files_to_build)
+
+def _test_srcs_can_contain_rule_generating_py_and_nonpy_files(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = ["lib.py", name + "_gensrcs"],
+ )
+ rt_util.helper_target(
+ native.genrule,
+ name = name + "_gensrcs",
+ cmd = "touch $(OUTS)",
+ outs = [name + "_gen.py", name + "_gen.cc"],
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_srcs_can_contain_rule_generating_py_and_nonpy_files_impl,
+ )
+
+def _test_srcs_can_contain_rule_generating_py_and_nonpy_files_impl(env, target):
+ env.expect.that_target(target).default_outputs().contains_exactly([
+ "{package}/{test_name}_gen.py",
+ "{package}/lib.py",
+ ])
+
+_tests.append(_test_srcs_can_contain_rule_generating_py_and_nonpy_files)
+
+def _test_srcs_generating_no_py_files_is_error(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = [name + "_gen"],
+ )
+ rt_util.helper_target(
+ native.genrule,
+ name = name + "_gen",
+ cmd = "touch $(OUTS)",
+ outs = [name + "_gen.cc"],
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_srcs_generating_no_py_files_is_error_impl,
+ expect_failure = True,
+ )
+
+def _test_srcs_generating_no_py_files_is_error_impl(env, target):
+ env.expect.that_target(target).failures().contains_predicate(
+ matching.str_matches("does not produce*srcs files"),
+ )
+
+_tests.append(_test_srcs_generating_no_py_files_is_error)
+
+def _test_files_to_compile(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = ["lib1.py"],
+ deps = [name + "_lib2"],
+ )
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_lib2",
+ srcs = ["lib2.py"],
+ deps = [name + "_lib3"],
+ )
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_lib3",
+ srcs = ["lib3.py"],
+ )
+ analysis_test(
+ name = name,
+ target = name + "_subject",
+ impl = _test_files_to_compile_impl,
+ )
+
+def _test_files_to_compile_impl(env, target):
+ target = env.expect.that_target(target)
+ target.output_group(
+ "compilation_prerequisites_INTERNAL_",
+ ).contains_exactly([
+ "{package}/lib1.py",
+ "{package}/lib2.py",
+ "{package}/lib3.py",
+ ])
+ target.output_group(
+ "compilation_outputs",
+ ).contains_exactly([
+ "{package}/lib1.py",
+ "{package}/lib2.py",
+ "{package}/lib3.py",
+ ])
+
+_tests.append(_test_files_to_compile)
+
+def py_library_test_suite(name):
+ config = struct(rule = py_library, base_test_rule = py_library)
+ native.test_suite(
+ name = name,
+ tests = pt_util.create_tests(_tests, config = config) + create_base_tests(config),
+ )
diff --git a/tools/build_defs/python/tests/py_test/BUILD.bazel b/tools/build_defs/python/tests/py_test/BUILD.bazel
new file mode 100644
index 0000000..2dc0e5b
--- /dev/null
+++ b/tools/build_defs/python/tests/py_test/BUILD.bazel
@@ -0,0 +1,18 @@
+# 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.
+"""Tests for py_test."""
+
+load(":py_test_tests.bzl", "py_test_test_suite")
+
+py_test_test_suite(name = "py_test_tests")
diff --git a/tools/build_defs/python/tests/py_test/py_test_tests.bzl b/tools/build_defs/python/tests/py_test/py_test_tests.bzl
new file mode 100644
index 0000000..1ecb252
--- /dev/null
+++ b/tools/build_defs/python/tests/py_test/py_test_tests.bzl
@@ -0,0 +1,107 @@
+# 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.
+"""Tests for py_test."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
+load("@rules_testing//lib:util.bzl", rt_util = "util")
+load("//python:defs.bzl", "py_test")
+load(
+ "//tools/build_defs/python/tests:py_executable_base_tests.bzl",
+ "create_executable_tests",
+)
+load("//tools/build_defs/python/tests:util.bzl", pt_util = "util")
+
+# Explicit Label() calls are required so that it resolves in @rules_python context instead of
+# @rules_testing context.
+_FAKE_CC_TOOLCHAIN = Label("//tests/cc:cc_toolchain_suite")
+_FAKE_CC_TOOLCHAINS = [str(Label("//tests/cc:all"))]
+_PLATFORM_MAC = Label("//tools/build_defs/python/tests:mac")
+_PLATFORM_LINUX = Label("//tools/build_defs/python/tests:linux")
+
+_tests = []
+
+def _test_mac_requires_darwin_for_execution(name, config):
+ # Bazel 5.4 has a bug where every access of testing.ExecutionInfo is
+ # a different object that isn't equal to any other, which prevents
+ # rules_testing from detecting it properly and fails with an error.
+ # This is fixed in Bazel 6+.
+ if not pt_util.is_bazel_6_or_higher():
+ rt_util.skip_test(name = name)
+ return
+
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = [name + "_subject.py"],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_mac_requires_darwin_for_execution_impl,
+ target = name + "_subject",
+ config_settings = {
+ "//command_line_option:cpu": "darwin_x86_64",
+ "//command_line_option:crosstool_top": _FAKE_CC_TOOLCHAIN,
+ "//command_line_option:extra_toolchains": _FAKE_CC_TOOLCHAINS,
+ "//command_line_option:platforms": [_PLATFORM_MAC],
+ },
+ )
+
+def _test_mac_requires_darwin_for_execution_impl(env, target):
+ env.expect.that_target(target).provider(
+ testing.ExecutionInfo,
+ ).requirements().keys().contains("requires-darwin")
+
+_tests.append(_test_mac_requires_darwin_for_execution)
+
+def _test_non_mac_doesnt_require_darwin_for_execution(name, config):
+ # Bazel 5.4 has a bug where every access of testing.ExecutionInfo is
+ # a different object that isn't equal to any other, which prevents
+ # rules_testing from detecting it properly and fails with an error.
+ # This is fixed in Bazel 6+.
+ if not pt_util.is_bazel_6_or_higher():
+ rt_util.skip_test(name = name)
+ return
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_subject",
+ srcs = [name + "_subject.py"],
+ )
+ analysis_test(
+ name = name,
+ impl = _test_non_mac_doesnt_require_darwin_for_execution_impl,
+ target = name + "_subject",
+ config_settings = {
+ "//command_line_option:cpu": "k8",
+ "//command_line_option:crosstool_top": _FAKE_CC_TOOLCHAIN,
+ "//command_line_option:extra_toolchains": _FAKE_CC_TOOLCHAINS,
+ "//command_line_option:platforms": [_PLATFORM_LINUX],
+ },
+ )
+
+def _test_non_mac_doesnt_require_darwin_for_execution_impl(env, target):
+ # Non-mac builds don't have the provider at all.
+ if testing.ExecutionInfo not in target:
+ return
+ env.expect.that_target(target).provider(
+ testing.ExecutionInfo,
+ ).requirements().keys().not_contains("requires-darwin")
+
+_tests.append(_test_non_mac_doesnt_require_darwin_for_execution)
+
+def py_test_test_suite(name):
+ config = struct(rule = py_test)
+ native.test_suite(
+ name = name,
+ tests = pt_util.create_tests(_tests, config = config) + create_executable_tests(config),
+ )
diff --git a/tools/build_defs/python/tests/py_wheel/BUILD.bazel b/tools/build_defs/python/tests/py_wheel/BUILD.bazel
new file mode 100644
index 0000000..d925bb9
--- /dev/null
+++ b/tools/build_defs/python/tests/py_wheel/BUILD.bazel
@@ -0,0 +1,18 @@
+# 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.
+"""Tests for py_wheel."""
+
+load(":py_wheel_tests.bzl", "py_wheel_test_suite")
+
+py_wheel_test_suite(name = "py_wheel_tests")
diff --git a/tools/build_defs/python/tests/py_wheel/py_wheel_tests.bzl b/tools/build_defs/python/tests/py_wheel/py_wheel_tests.bzl
new file mode 100644
index 0000000..4408592
--- /dev/null
+++ b/tools/build_defs/python/tests/py_wheel/py_wheel_tests.bzl
@@ -0,0 +1,39 @@
+"""Test for py_wheel."""
+
+load("@rules_testing//lib:analysis_test.bzl", "analysis_test")
+load("@rules_testing//lib:truth.bzl", "matching")
+load("@rules_testing//lib:util.bzl", rt_util = "util")
+load("//python:packaging.bzl", "py_wheel")
+load("//tools/build_defs/python/tests:util.bzl", pt_util = "util")
+
+_tests = []
+
+def _test_too_long_project_url_label(name, config):
+ rt_util.helper_target(
+ config.rule,
+ name = name + "_wheel",
+ distribution = name + "_wheel",
+ python_tag = "py3",
+ version = "0.0.1",
+ project_urls = {"This is a label whose length is above the limit!": "www.example.com"},
+ )
+ analysis_test(
+ name = name,
+ target = name + "_wheel",
+ impl = _test_too_long_project_url_label_impl,
+ expect_failure = True,
+ )
+
+def _test_too_long_project_url_label_impl(env, target):
+ env.expect.that_target(target).failures().contains_predicate(
+ matching.str_matches("in `project_urls` is too long"),
+ )
+
+_tests.append(_test_too_long_project_url_label)
+
+def py_wheel_test_suite(name):
+ config = struct(rule = py_wheel, base_test_rule = py_wheel)
+ native.test_suite(
+ name = name,
+ tests = pt_util.create_tests(_tests, config = config),
+ )
diff --git a/tools/build_defs/python/tests/util.bzl b/tools/build_defs/python/tests/util.bzl
new file mode 100644
index 0000000..9b386ca
--- /dev/null
+++ b/tools/build_defs/python/tests/util.bzl
@@ -0,0 +1,78 @@
+# 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.
+"""Helpers and utilities multiple tests re-use."""
+
+load("@bazel_skylib//lib:structs.bzl", "structs")
+
+# Use this with is_windows()
+WINDOWS_ATTR = {"windows": attr.label(default = "@platforms//os:windows")}
+
+def _create_tests(tests, **kwargs):
+ test_names = []
+ for func in tests:
+ test_name = _test_name_from_function(func)
+ func(name = test_name, **kwargs)
+ test_names.append(test_name)
+ return test_names
+
+def _test_name_from_function(func):
+ """Derives the name of the given rule implementation function.
+
+ Args:
+ func: the function whose name to extract
+
+ Returns:
+ The name of the given function. Note it will have leading and trailing
+ "_" stripped -- this allows passing a private function and having the
+ name of the test not start with "_".
+ """
+
+ # Starlark currently stringifies a function as "<function NAME>", so we use
+ # that knowledge to parse the "NAME" portion out.
+ # NOTE: This is relying on an implementation detail of Bazel
+ func_name = str(func)
+ func_name = func_name.partition("<function ")[-1]
+ func_name = func_name.rpartition(">")[0]
+ func_name = func_name.partition(" ")[0]
+ return func_name.strip("_")
+
+def _struct_with(s, **kwargs):
+ struct_dict = structs.to_dict(s)
+ struct_dict.update(kwargs)
+ return struct(**struct_dict)
+
+def _is_bazel_6_or_higher():
+ # Bazel 5.4 has a bug where every access of testing.ExecutionInfo is a
+ # different object that isn't equal to any other. This is fixed in bazel 6+.
+ return testing.ExecutionInfo == testing.ExecutionInfo
+
+def _is_windows(env):
+ """Tell if the target platform is windows.
+
+ This assumes the `WINDOWS_ATTR` attribute was added.
+
+ Args:
+ env: The test env struct
+ Returns:
+ True if the target is Windows, False if not.
+ """
+ constraint = env.ctx.attr.windows[platform_common.ConstraintValueInfo]
+ return env.ctx.target_platform_has_constraint(constraint)
+
+util = struct(
+ create_tests = _create_tests,
+ struct_with = _struct_with,
+ is_bazel_6_or_higher = _is_bazel_6_or_higher,
+ is_windows = _is_windows,
+)
diff --git a/tools/publish/BUILD.bazel b/tools/publish/BUILD.bazel
new file mode 100644
index 0000000..065e56b
--- /dev/null
+++ b/tools/publish/BUILD.bazel
@@ -0,0 +1,7 @@
+load("//python:pip.bzl", "compile_pip_requirements")
+
+compile_pip_requirements(
+ name = "requirements",
+ requirements_darwin = "requirements_darwin.txt",
+ requirements_windows = "requirements_windows.txt",
+)
diff --git a/tools/publish/README.md b/tools/publish/README.md
new file mode 100644
index 0000000..6f1e549
--- /dev/null
+++ b/tools/publish/README.md
@@ -0,0 +1,6 @@
+# Publish to pypi with twine
+
+https://packaging.python.org/en/latest/tutorials/packaging-projects/ indicates that the twine
+package is used to publish wheels to pypi.
+
+See more: https://twine.readthedocs.io/en/stable/
diff --git a/tools/publish/requirements.in b/tools/publish/requirements.in
new file mode 100644
index 0000000..af996cf
--- /dev/null
+++ b/tools/publish/requirements.in
@@ -0,0 +1 @@
+twine
diff --git a/tools/publish/requirements.txt b/tools/publish/requirements.txt
new file mode 100644
index 0000000..858fc51
--- /dev/null
+++ b/tools/publish/requirements.txt
@@ -0,0 +1,297 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# bazel run //tools/publish:requirements.update
+#
+bleach==6.0.0 \
+ --hash=sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414 \
+ --hash=sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4
+ # via readme-renderer
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+cffi==1.15.1 \
+ --hash=sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5 \
+ --hash=sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef \
+ --hash=sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104 \
+ --hash=sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426 \
+ --hash=sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405 \
+ --hash=sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375 \
+ --hash=sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a \
+ --hash=sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e \
+ --hash=sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc \
+ --hash=sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf \
+ --hash=sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185 \
+ --hash=sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497 \
+ --hash=sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3 \
+ --hash=sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35 \
+ --hash=sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c \
+ --hash=sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83 \
+ --hash=sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21 \
+ --hash=sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca \
+ --hash=sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984 \
+ --hash=sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac \
+ --hash=sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd \
+ --hash=sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee \
+ --hash=sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a \
+ --hash=sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2 \
+ --hash=sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192 \
+ --hash=sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7 \
+ --hash=sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585 \
+ --hash=sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f \
+ --hash=sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e \
+ --hash=sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27 \
+ --hash=sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b \
+ --hash=sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e \
+ --hash=sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e \
+ --hash=sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d \
+ --hash=sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c \
+ --hash=sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415 \
+ --hash=sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82 \
+ --hash=sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02 \
+ --hash=sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314 \
+ --hash=sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325 \
+ --hash=sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c \
+ --hash=sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3 \
+ --hash=sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914 \
+ --hash=sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045 \
+ --hash=sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d \
+ --hash=sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9 \
+ --hash=sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5 \
+ --hash=sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2 \
+ --hash=sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c \
+ --hash=sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3 \
+ --hash=sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2 \
+ --hash=sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8 \
+ --hash=sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d \
+ --hash=sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d \
+ --hash=sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9 \
+ --hash=sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162 \
+ --hash=sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76 \
+ --hash=sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4 \
+ --hash=sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e \
+ --hash=sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9 \
+ --hash=sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6 \
+ --hash=sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b \
+ --hash=sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01 \
+ --hash=sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0
+ # via cryptography
+charset-normalizer==3.0.1 \
+ --hash=sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b \
+ --hash=sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42 \
+ --hash=sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d \
+ --hash=sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b \
+ --hash=sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a \
+ --hash=sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59 \
+ --hash=sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154 \
+ --hash=sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1 \
+ --hash=sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c \
+ --hash=sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a \
+ --hash=sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d \
+ --hash=sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6 \
+ --hash=sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b \
+ --hash=sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b \
+ --hash=sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783 \
+ --hash=sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5 \
+ --hash=sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918 \
+ --hash=sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555 \
+ --hash=sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639 \
+ --hash=sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786 \
+ --hash=sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e \
+ --hash=sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed \
+ --hash=sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820 \
+ --hash=sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8 \
+ --hash=sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3 \
+ --hash=sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541 \
+ --hash=sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14 \
+ --hash=sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be \
+ --hash=sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e \
+ --hash=sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76 \
+ --hash=sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b \
+ --hash=sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c \
+ --hash=sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b \
+ --hash=sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3 \
+ --hash=sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc \
+ --hash=sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6 \
+ --hash=sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59 \
+ --hash=sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4 \
+ --hash=sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d \
+ --hash=sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d \
+ --hash=sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3 \
+ --hash=sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a \
+ --hash=sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea \
+ --hash=sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6 \
+ --hash=sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e \
+ --hash=sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603 \
+ --hash=sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24 \
+ --hash=sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a \
+ --hash=sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58 \
+ --hash=sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678 \
+ --hash=sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a \
+ --hash=sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c \
+ --hash=sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6 \
+ --hash=sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18 \
+ --hash=sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174 \
+ --hash=sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317 \
+ --hash=sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f \
+ --hash=sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc \
+ --hash=sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837 \
+ --hash=sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41 \
+ --hash=sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c \
+ --hash=sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579 \
+ --hash=sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753 \
+ --hash=sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8 \
+ --hash=sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291 \
+ --hash=sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087 \
+ --hash=sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866 \
+ --hash=sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3 \
+ --hash=sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d \
+ --hash=sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1 \
+ --hash=sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca \
+ --hash=sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e \
+ --hash=sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db \
+ --hash=sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72 \
+ --hash=sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d \
+ --hash=sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc \
+ --hash=sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539 \
+ --hash=sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d \
+ --hash=sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af \
+ --hash=sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b \
+ --hash=sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602 \
+ --hash=sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f \
+ --hash=sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478 \
+ --hash=sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c \
+ --hash=sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e \
+ --hash=sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479 \
+ --hash=sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7 \
+ --hash=sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8
+ # via requests
+cryptography==39.0.0 \
+ --hash=sha256:1a6915075c6d3a5e1215eab5d99bcec0da26036ff2102a1038401d6ef5bef25b \
+ --hash=sha256:1ee1fd0de9851ff32dbbb9362a4d833b579b4a6cc96883e8e6d2ff2a6bc7104f \
+ --hash=sha256:407cec680e811b4fc829de966f88a7c62a596faa250fc1a4b520a0355b9bc190 \
+ --hash=sha256:50386acb40fbabbceeb2986332f0287f50f29ccf1497bae31cf5c3e7b4f4b34f \
+ --hash=sha256:6f97109336df5c178ee7c9c711b264c502b905c2d2a29ace99ed761533a3460f \
+ --hash=sha256:754978da4d0457e7ca176f58c57b1f9de6556591c19b25b8bcce3c77d314f5eb \
+ --hash=sha256:76c24dd4fd196a80f9f2f5405a778a8ca132f16b10af113474005635fe7e066c \
+ --hash=sha256:7dacfdeee048814563eaaec7c4743c8aea529fe3dd53127313a792f0dadc1773 \
+ --hash=sha256:80ee674c08aaef194bc4627b7f2956e5ba7ef29c3cc3ca488cf15854838a8f72 \
+ --hash=sha256:844ad4d7c3850081dffba91cdd91950038ee4ac525c575509a42d3fc806b83c8 \
+ --hash=sha256:875aea1039d78557c7c6b4db2fe0e9d2413439f4676310a5f269dd342ca7a717 \
+ --hash=sha256:887cbc1ea60786e534b00ba8b04d1095f4272d380ebd5f7a7eb4cc274710fad9 \
+ --hash=sha256:ad04f413436b0781f20c52a661660f1e23bcd89a0e9bb1d6d20822d048cf2856 \
+ --hash=sha256:bae6c7f4a36a25291b619ad064a30a07110a805d08dc89984f4f441f6c1f3f96 \
+ --hash=sha256:c52a1a6f81e738d07f43dab57831c29e57d21c81a942f4602fac7ee21b27f288 \
+ --hash=sha256:e0a05aee6a82d944f9b4edd6a001178787d1546ec7c6223ee9a848a7ade92e39 \
+ --hash=sha256:e324de6972b151f99dc078defe8fb1b0a82c6498e37bff335f5bc6b1e3ab5a1e \
+ --hash=sha256:e5d71c5d5bd5b5c3eebcf7c5c2bb332d62ec68921a8c593bea8c394911a005ce \
+ --hash=sha256:f3ed2d864a2fa1666e749fe52fb8e23d8e06b8012e8bd8147c73797c506e86f1 \
+ --hash=sha256:f671c1bb0d6088e94d61d80c606d65baacc0d374e67bf895148883461cd848de \
+ --hash=sha256:f6c0db08d81ead9576c4d94bbb27aed8d7a430fa27890f39084c2d0e2ec6b0df \
+ --hash=sha256:f964c7dcf7802d133e8dbd1565914fa0194f9d683d82411989889ecd701e8adf \
+ --hash=sha256:fec8b932f51ae245121c4671b4bbc030880f363354b2f0e0bd1366017d891458
+ # via secretstorage
+docutils==0.19 \
+ --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \
+ --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc
+ # via readme-renderer
+idna==3.4 \
+ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+ # via requests
+importlib-metadata==6.0.0 \
+ --hash=sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad \
+ --hash=sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d
+ # via
+ # keyring
+ # twine
+jaraco-classes==3.2.3 \
+ --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \
+ --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a
+ # via keyring
+jeepney==0.8.0 \
+ --hash=sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806 \
+ --hash=sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755
+ # via
+ # keyring
+ # secretstorage
+keyring==23.13.1 \
+ --hash=sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd \
+ --hash=sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678
+ # via twine
+markdown-it-py==2.1.0 \
+ --hash=sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27 \
+ --hash=sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da
+ # via rich
+mdurl==0.1.2 \
+ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
+ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
+ # via markdown-it-py
+more-itertools==9.0.0 \
+ --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \
+ --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab
+ # via jaraco-classes
+pkginfo==1.9.6 \
+ --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \
+ --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046
+ # via twine
+pycparser==2.21 \
+ --hash=sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9 \
+ --hash=sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206
+ # via cffi
+pygments==2.14.0 \
+ --hash=sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297 \
+ --hash=sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717
+ # via
+ # readme-renderer
+ # rich
+readme-renderer==37.3 \
+ --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \
+ --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343
+ # via twine
+requests==2.28.2 \
+ --hash=sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa \
+ --hash=sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf
+ # via
+ # requests-toolbelt
+ # twine
+requests-toolbelt==0.10.1 \
+ --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \
+ --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d
+ # via twine
+rfc3986==2.0.0 \
+ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \
+ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c
+ # via twine
+rich==13.2.0 \
+ --hash=sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003 \
+ --hash=sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5
+ # via twine
+secretstorage==3.3.3 \
+ --hash=sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77 \
+ --hash=sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99
+ # via keyring
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+twine==4.0.2 \
+ --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \
+ --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8
+ # via -r tools/publish/requirements.in
+urllib3==1.26.14 \
+ --hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
+ --hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
+ # via
+ # requests
+ # twine
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via bleach
+zipp==3.11.0 \
+ --hash=sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa \
+ --hash=sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766
+ # via importlib-metadata
diff --git a/tools/publish/requirements_darwin.txt b/tools/publish/requirements_darwin.txt
new file mode 100644
index 0000000..cb35e69
--- /dev/null
+++ b/tools/publish/requirements_darwin.txt
@@ -0,0 +1,192 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# bazel run //tools/publish:requirements.update
+#
+bleach==6.0.0 \
+ --hash=sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414 \
+ --hash=sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4
+ # via readme-renderer
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+charset-normalizer==3.0.1 \
+ --hash=sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b \
+ --hash=sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42 \
+ --hash=sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d \
+ --hash=sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b \
+ --hash=sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a \
+ --hash=sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59 \
+ --hash=sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154 \
+ --hash=sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1 \
+ --hash=sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c \
+ --hash=sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a \
+ --hash=sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d \
+ --hash=sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6 \
+ --hash=sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b \
+ --hash=sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b \
+ --hash=sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783 \
+ --hash=sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5 \
+ --hash=sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918 \
+ --hash=sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555 \
+ --hash=sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639 \
+ --hash=sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786 \
+ --hash=sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e \
+ --hash=sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed \
+ --hash=sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820 \
+ --hash=sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8 \
+ --hash=sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3 \
+ --hash=sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541 \
+ --hash=sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14 \
+ --hash=sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be \
+ --hash=sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e \
+ --hash=sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76 \
+ --hash=sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b \
+ --hash=sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c \
+ --hash=sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b \
+ --hash=sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3 \
+ --hash=sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc \
+ --hash=sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6 \
+ --hash=sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59 \
+ --hash=sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4 \
+ --hash=sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d \
+ --hash=sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d \
+ --hash=sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3 \
+ --hash=sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a \
+ --hash=sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea \
+ --hash=sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6 \
+ --hash=sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e \
+ --hash=sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603 \
+ --hash=sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24 \
+ --hash=sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a \
+ --hash=sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58 \
+ --hash=sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678 \
+ --hash=sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a \
+ --hash=sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c \
+ --hash=sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6 \
+ --hash=sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18 \
+ --hash=sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174 \
+ --hash=sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317 \
+ --hash=sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f \
+ --hash=sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc \
+ --hash=sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837 \
+ --hash=sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41 \
+ --hash=sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c \
+ --hash=sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579 \
+ --hash=sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753 \
+ --hash=sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8 \
+ --hash=sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291 \
+ --hash=sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087 \
+ --hash=sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866 \
+ --hash=sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3 \
+ --hash=sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d \
+ --hash=sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1 \
+ --hash=sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca \
+ --hash=sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e \
+ --hash=sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db \
+ --hash=sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72 \
+ --hash=sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d \
+ --hash=sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc \
+ --hash=sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539 \
+ --hash=sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d \
+ --hash=sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af \
+ --hash=sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b \
+ --hash=sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602 \
+ --hash=sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f \
+ --hash=sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478 \
+ --hash=sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c \
+ --hash=sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e \
+ --hash=sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479 \
+ --hash=sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7 \
+ --hash=sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8
+ # via requests
+docutils==0.19 \
+ --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \
+ --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc
+ # via readme-renderer
+idna==3.4 \
+ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+ # via requests
+importlib-metadata==6.0.0 \
+ --hash=sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad \
+ --hash=sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d
+ # via
+ # keyring
+ # twine
+jaraco-classes==3.2.3 \
+ --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \
+ --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a
+ # via keyring
+keyring==23.13.1 \
+ --hash=sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd \
+ --hash=sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678
+ # via twine
+markdown-it-py==2.1.0 \
+ --hash=sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27 \
+ --hash=sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da
+ # via rich
+mdurl==0.1.2 \
+ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
+ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
+ # via markdown-it-py
+more-itertools==9.0.0 \
+ --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \
+ --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab
+ # via jaraco-classes
+pkginfo==1.9.6 \
+ --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \
+ --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046
+ # via twine
+pygments==2.14.0 \
+ --hash=sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297 \
+ --hash=sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717
+ # via
+ # readme-renderer
+ # rich
+readme-renderer==37.3 \
+ --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \
+ --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343
+ # via twine
+requests==2.28.2 \
+ --hash=sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa \
+ --hash=sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf
+ # via
+ # requests-toolbelt
+ # twine
+requests-toolbelt==0.10.1 \
+ --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \
+ --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d
+ # via twine
+rfc3986==2.0.0 \
+ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \
+ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c
+ # via twine
+rich==13.2.0 \
+ --hash=sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003 \
+ --hash=sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5
+ # via twine
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+twine==4.0.2 \
+ --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \
+ --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8
+ # via -r tools/publish/requirements.in
+urllib3==1.26.14 \
+ --hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
+ --hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
+ # via
+ # requests
+ # twine
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via bleach
+zipp==3.11.0 \
+ --hash=sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa \
+ --hash=sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766
+ # via importlib-metadata
diff --git a/tools/publish/requirements_windows.txt b/tools/publish/requirements_windows.txt
new file mode 100644
index 0000000..cd175c6
--- /dev/null
+++ b/tools/publish/requirements_windows.txt
@@ -0,0 +1,196 @@
+#
+# This file is autogenerated by pip-compile with Python 3.11
+# by the following command:
+#
+# bazel run //tools/publish:requirements.update
+#
+bleach==6.0.0 \
+ --hash=sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414 \
+ --hash=sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4
+ # via readme-renderer
+certifi==2022.12.7 \
+ --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
+ --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
+ # via requests
+charset-normalizer==3.0.1 \
+ --hash=sha256:00d3ffdaafe92a5dc603cb9bd5111aaa36dfa187c8285c543be562e61b755f6b \
+ --hash=sha256:024e606be3ed92216e2b6952ed859d86b4cfa52cd5bc5f050e7dc28f9b43ec42 \
+ --hash=sha256:0298eafff88c99982a4cf66ba2efa1128e4ddaca0b05eec4c456bbc7db691d8d \
+ --hash=sha256:02a51034802cbf38db3f89c66fb5d2ec57e6fe7ef2f4a44d070a593c3688667b \
+ --hash=sha256:083c8d17153ecb403e5e1eb76a7ef4babfc2c48d58899c98fcaa04833e7a2f9a \
+ --hash=sha256:0a11e971ed097d24c534c037d298ad32c6ce81a45736d31e0ff0ad37ab437d59 \
+ --hash=sha256:0bf2dae5291758b6f84cf923bfaa285632816007db0330002fa1de38bfcb7154 \
+ --hash=sha256:0c0a590235ccd933d9892c627dec5bc7511ce6ad6c1011fdf5b11363022746c1 \
+ --hash=sha256:0f438ae3532723fb6ead77e7c604be7c8374094ef4ee2c5e03a3a17f1fca256c \
+ --hash=sha256:109487860ef6a328f3eec66f2bf78b0b72400280d8f8ea05f69c51644ba6521a \
+ --hash=sha256:11b53acf2411c3b09e6af37e4b9005cba376c872503c8f28218c7243582df45d \
+ --hash=sha256:12db3b2c533c23ab812c2b25934f60383361f8a376ae272665f8e48b88e8e1c6 \
+ --hash=sha256:14e76c0f23218b8f46c4d87018ca2e441535aed3632ca134b10239dfb6dadd6b \
+ --hash=sha256:16a8663d6e281208d78806dbe14ee9903715361cf81f6d4309944e4d1e59ac5b \
+ --hash=sha256:292d5e8ba896bbfd6334b096e34bffb56161c81408d6d036a7dfa6929cff8783 \
+ --hash=sha256:2c03cc56021a4bd59be889c2b9257dae13bf55041a3372d3295416f86b295fb5 \
+ --hash=sha256:2e396d70bc4ef5325b72b593a72c8979999aa52fb8bcf03f701c1b03e1166918 \
+ --hash=sha256:2edb64ee7bf1ed524a1da60cdcd2e1f6e2b4f66ef7c077680739f1641f62f555 \
+ --hash=sha256:31a9ddf4718d10ae04d9b18801bd776693487cbb57d74cc3458a7673f6f34639 \
+ --hash=sha256:356541bf4381fa35856dafa6a965916e54bed415ad8a24ee6de6e37deccf2786 \
+ --hash=sha256:358a7c4cb8ba9b46c453b1dd8d9e431452d5249072e4f56cfda3149f6ab1405e \
+ --hash=sha256:37f8febc8ec50c14f3ec9637505f28e58d4f66752207ea177c1d67df25da5aed \
+ --hash=sha256:39049da0ffb96c8cbb65cbf5c5f3ca3168990adf3551bd1dee10c48fce8ae820 \
+ --hash=sha256:39cf9ed17fe3b1bc81f33c9ceb6ce67683ee7526e65fde1447c772afc54a1bb8 \
+ --hash=sha256:3ae1de54a77dc0d6d5fcf623290af4266412a7c4be0b1ff7444394f03f5c54e3 \
+ --hash=sha256:3b590df687e3c5ee0deef9fc8c547d81986d9a1b56073d82de008744452d6541 \
+ --hash=sha256:3e45867f1f2ab0711d60c6c71746ac53537f1684baa699f4f668d4c6f6ce8e14 \
+ --hash=sha256:3fc1c4a2ffd64890aebdb3f97e1278b0cc72579a08ca4de8cd2c04799a3a22be \
+ --hash=sha256:4457ea6774b5611f4bed5eaa5df55f70abde42364d498c5134b7ef4c6958e20e \
+ --hash=sha256:44ba614de5361b3e5278e1241fda3dc1838deed864b50a10d7ce92983797fa76 \
+ --hash=sha256:4a8fcf28c05c1f6d7e177a9a46a1c52798bfe2ad80681d275b10dcf317deaf0b \
+ --hash=sha256:4b0d02d7102dd0f997580b51edc4cebcf2ab6397a7edf89f1c73b586c614272c \
+ --hash=sha256:502218f52498a36d6bf5ea77081844017bf7982cdbe521ad85e64cabee1b608b \
+ --hash=sha256:503e65837c71b875ecdd733877d852adbc465bd82c768a067badd953bf1bc5a3 \
+ --hash=sha256:5995f0164fa7df59db4746112fec3f49c461dd6b31b841873443bdb077c13cfc \
+ --hash=sha256:59e5686dd847347e55dffcc191a96622f016bc0ad89105e24c14e0d6305acbc6 \
+ --hash=sha256:601f36512f9e28f029d9481bdaf8e89e5148ac5d89cffd3b05cd533eeb423b59 \
+ --hash=sha256:608862a7bf6957f2333fc54ab4399e405baad0163dc9f8d99cb236816db169d4 \
+ --hash=sha256:62595ab75873d50d57323a91dd03e6966eb79c41fa834b7a1661ed043b2d404d \
+ --hash=sha256:70990b9c51340e4044cfc394a81f614f3f90d41397104d226f21e66de668730d \
+ --hash=sha256:71140351489970dfe5e60fc621ada3e0f41104a5eddaca47a7acb3c1b851d6d3 \
+ --hash=sha256:72966d1b297c741541ca8cf1223ff262a6febe52481af742036a0b296e35fa5a \
+ --hash=sha256:74292fc76c905c0ef095fe11e188a32ebd03bc38f3f3e9bcb85e4e6db177b7ea \
+ --hash=sha256:761e8904c07ad053d285670f36dd94e1b6ab7f16ce62b9805c475b7aa1cffde6 \
+ --hash=sha256:772b87914ff1152b92a197ef4ea40efe27a378606c39446ded52c8f80f79702e \
+ --hash=sha256:79909e27e8e4fcc9db4addea88aa63f6423ebb171db091fb4373e3312cb6d603 \
+ --hash=sha256:7e189e2e1d3ed2f4aebabd2d5b0f931e883676e51c7624826e0a4e5fe8a0bf24 \
+ --hash=sha256:7eb33a30d75562222b64f569c642ff3dc6689e09adda43a082208397f016c39a \
+ --hash=sha256:81d6741ab457d14fdedc215516665050f3822d3e56508921cc7239f8c8e66a58 \
+ --hash=sha256:8499ca8f4502af841f68135133d8258f7b32a53a1d594aa98cc52013fff55678 \
+ --hash=sha256:84c3990934bae40ea69a82034912ffe5a62c60bbf6ec5bc9691419641d7d5c9a \
+ --hash=sha256:87701167f2a5c930b403e9756fab1d31d4d4da52856143b609e30a1ce7160f3c \
+ --hash=sha256:88600c72ef7587fe1708fd242b385b6ed4b8904976d5da0893e31df8b3480cb6 \
+ --hash=sha256:8ac7b6a045b814cf0c47f3623d21ebd88b3e8cf216a14790b455ea7ff0135d18 \
+ --hash=sha256:8b8af03d2e37866d023ad0ddea594edefc31e827fee64f8de5611a1dbc373174 \
+ --hash=sha256:8c7fe7afa480e3e82eed58e0ca89f751cd14d767638e2550c77a92a9e749c317 \
+ --hash=sha256:8eade758719add78ec36dc13201483f8e9b5d940329285edcd5f70c0a9edbd7f \
+ --hash=sha256:911d8a40b2bef5b8bbae2e36a0b103f142ac53557ab421dc16ac4aafee6f53dc \
+ --hash=sha256:93ad6d87ac18e2a90b0fe89df7c65263b9a99a0eb98f0a3d2e079f12a0735837 \
+ --hash=sha256:95dea361dd73757c6f1c0a1480ac499952c16ac83f7f5f4f84f0658a01b8ef41 \
+ --hash=sha256:9ab77acb98eba3fd2a85cd160851816bfce6871d944d885febf012713f06659c \
+ --hash=sha256:9cb3032517f1627cc012dbc80a8ec976ae76d93ea2b5feaa9d2a5b8882597579 \
+ --hash=sha256:9cf4e8ad252f7c38dd1f676b46514f92dc0ebeb0db5552f5f403509705e24753 \
+ --hash=sha256:9d9153257a3f70d5f69edf2325357251ed20f772b12e593f3b3377b5f78e7ef8 \
+ --hash=sha256:a152f5f33d64a6be73f1d30c9cc82dfc73cec6477ec268e7c6e4c7d23c2d2291 \
+ --hash=sha256:a16418ecf1329f71df119e8a65f3aa68004a3f9383821edcb20f0702934d8087 \
+ --hash=sha256:a60332922359f920193b1d4826953c507a877b523b2395ad7bc716ddd386d866 \
+ --hash=sha256:a8d0fc946c784ff7f7c3742310cc8a57c5c6dc31631269876a88b809dbeff3d3 \
+ --hash=sha256:ab5de034a886f616a5668aa5d098af2b5385ed70142090e2a31bcbd0af0fdb3d \
+ --hash=sha256:c22d3fe05ce11d3671297dc8973267daa0f938b93ec716e12e0f6dee81591dc1 \
+ --hash=sha256:c2ac1b08635a8cd4e0cbeaf6f5e922085908d48eb05d44c5ae9eabab148512ca \
+ --hash=sha256:c512accbd6ff0270939b9ac214b84fb5ada5f0409c44298361b2f5e13f9aed9e \
+ --hash=sha256:c75ffc45f25324e68ab238cb4b5c0a38cd1c3d7f1fb1f72b5541de469e2247db \
+ --hash=sha256:c95a03c79bbe30eec3ec2b7f076074f4281526724c8685a42872974ef4d36b72 \
+ --hash=sha256:cadaeaba78750d58d3cc6ac4d1fd867da6fc73c88156b7a3212a3cd4819d679d \
+ --hash=sha256:cd6056167405314a4dc3c173943f11249fa0f1b204f8b51ed4bde1a9cd1834dc \
+ --hash=sha256:db72b07027db150f468fbada4d85b3b2729a3db39178abf5c543b784c1254539 \
+ --hash=sha256:df2c707231459e8a4028eabcd3cfc827befd635b3ef72eada84ab13b52e1574d \
+ --hash=sha256:e62164b50f84e20601c1ff8eb55620d2ad25fb81b59e3cd776a1902527a788af \
+ --hash=sha256:e696f0dd336161fca9adbb846875d40752e6eba585843c768935ba5c9960722b \
+ --hash=sha256:eaa379fcd227ca235d04152ca6704c7cb55564116f8bc52545ff357628e10602 \
+ --hash=sha256:ebea339af930f8ca5d7a699b921106c6e29c617fe9606fa7baa043c1cdae326f \
+ --hash=sha256:f4c39b0e3eac288fedc2b43055cfc2ca7a60362d0e5e87a637beac5d801ef478 \
+ --hash=sha256:f5057856d21e7586765171eac8b9fc3f7d44ef39425f85dbcccb13b3ebea806c \
+ --hash=sha256:f6f45710b4459401609ebebdbcfb34515da4fc2aa886f95107f556ac69a9147e \
+ --hash=sha256:f97e83fa6c25693c7a35de154681fcc257c1c41b38beb0304b9c4d2d9e164479 \
+ --hash=sha256:f9d0c5c045a3ca9bedfc35dca8526798eb91a07aa7a2c0fee134c6c6f321cbd7 \
+ --hash=sha256:ff6f3db31555657f3163b15a6b7c6938d08df7adbfc9dd13d9d19edad678f1e8
+ # via requests
+docutils==0.19 \
+ --hash=sha256:33995a6753c30b7f577febfc2c50411fec6aac7f7ffeb7c4cfe5991072dcf9e6 \
+ --hash=sha256:5e1de4d849fee02c63b040a4a3fd567f4ab104defd8a5511fbbc24a8a017efbc
+ # via readme-renderer
+idna==3.4 \
+ --hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
+ --hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
+ # via requests
+importlib-metadata==6.0.0 \
+ --hash=sha256:7efb448ec9a5e313a57655d35aa54cd3e01b7e1fbcf72dce1bf06119420f5bad \
+ --hash=sha256:e354bedeb60efa6affdcc8ae121b73544a7aa74156d047311948f6d711cd378d
+ # via
+ # keyring
+ # twine
+jaraco-classes==3.2.3 \
+ --hash=sha256:2353de3288bc6b82120752201c6b1c1a14b058267fa424ed5ce5984e3b922158 \
+ --hash=sha256:89559fa5c1d3c34eff6f631ad80bb21f378dbcbb35dd161fd2c6b93f5be2f98a
+ # via keyring
+keyring==23.13.1 \
+ --hash=sha256:771ed2a91909389ed6148631de678f82ddc73737d85a927f382a8a1b157898cd \
+ --hash=sha256:ba2e15a9b35e21908d0aaf4e0a47acc52d6ae33444df0da2b49d41a46ef6d678
+ # via twine
+markdown-it-py==2.1.0 \
+ --hash=sha256:93de681e5c021a432c63147656fe21790bc01231e0cd2da73626f1aa3ac0fe27 \
+ --hash=sha256:cf7e59fed14b5ae17c0006eff14a2d9a00ed5f3a846148153899a0224e2c07da
+ # via rich
+mdurl==0.1.2 \
+ --hash=sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8 \
+ --hash=sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba
+ # via markdown-it-py
+more-itertools==9.0.0 \
+ --hash=sha256:250e83d7e81d0c87ca6bd942e6aeab8cc9daa6096d12c5308f3f92fa5e5c1f41 \
+ --hash=sha256:5a6257e40878ef0520b1803990e3e22303a41b5714006c32a3fd8304b26ea1ab
+ # via jaraco-classes
+pkginfo==1.9.6 \
+ --hash=sha256:4b7a555a6d5a22169fcc9cf7bfd78d296b0361adad412a346c1226849af5e546 \
+ --hash=sha256:8fd5896e8718a4372f0ea9cc9d96f6417c9b986e23a4d116dda26b62cc29d046
+ # via twine
+pygments==2.14.0 \
+ --hash=sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297 \
+ --hash=sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717
+ # via
+ # readme-renderer
+ # rich
+pywin32-ctypes==0.2.0 \
+ --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \
+ --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98
+ # via keyring
+readme-renderer==37.3 \
+ --hash=sha256:cd653186dfc73055656f090f227f5cb22a046d7f71a841dfa305f55c9a513273 \
+ --hash=sha256:f67a16caedfa71eef48a31b39708637a6f4664c4394801a7b0d6432d13907343
+ # via twine
+requests==2.28.2 \
+ --hash=sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa \
+ --hash=sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf
+ # via
+ # requests-toolbelt
+ # twine
+requests-toolbelt==0.10.1 \
+ --hash=sha256:18565aa58116d9951ac39baa288d3adb5b3ff975c4f25eee78555d89e8f247f7 \
+ --hash=sha256:62e09f7ff5ccbda92772a29f394a49c3ad6cb181d568b1337626b2abb628a63d
+ # via twine
+rfc3986==2.0.0 \
+ --hash=sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd \
+ --hash=sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c
+ # via twine
+rich==13.2.0 \
+ --hash=sha256:7c963f0d03819221e9ac561e1bc866e3f95a02248c1234daa48954e6d381c003 \
+ --hash=sha256:f1a00cdd3eebf999a15d85ec498bfe0b1a77efe9b34f645768a54132ef444ac5
+ # via twine
+six==1.16.0 \
+ --hash=sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926 \
+ --hash=sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254
+ # via bleach
+twine==4.0.2 \
+ --hash=sha256:929bc3c280033347a00f847236564d1c52a3e61b1ac2516c97c48f3ceab756d8 \
+ --hash=sha256:9e102ef5fdd5a20661eb88fad46338806c3bd32cf1db729603fe3697b1bc83c8
+ # via -r tools/publish/requirements.in
+urllib3==1.26.14 \
+ --hash=sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72 \
+ --hash=sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1
+ # via
+ # requests
+ # twine
+webencodings==0.5.1 \
+ --hash=sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78 \
+ --hash=sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923
+ # via bleach
+zipp==3.11.0 \
+ --hash=sha256:83a28fcb75844b5c0cdaf5aa4003c2d728c77e05f5aeabe8e95e56727005fbaa \
+ --hash=sha256:a7a22e05929290a67401440b39690ae6563279bced5f314609d9d03798f56766
+ # via importlib-metadata
diff --git a/tools/update_coverage_deps.py b/tools/update_coverage_deps.py
new file mode 100755
index 0000000..57b7850
--- /dev/null
+++ b/tools/update_coverage_deps.py
@@ -0,0 +1,248 @@
+#!/usr/bin/python3 -B
+# 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.
+
+"""A small script to update bazel files within the repo.
+
+We are not running this with 'bazel run' to keep the dependencies minimal
+"""
+
+# NOTE @aignas 2023-01-09: We should only depend on core Python 3 packages.
+import argparse
+import difflib
+import json
+import pathlib
+import sys
+import textwrap
+from collections import defaultdict
+from dataclasses import dataclass
+from typing import Any
+from urllib import request
+
+# This should be kept in sync with //python:versions.bzl
+_supported_platforms = {
+ # Windows is unsupported right now
+ # "win_amd64": "x86_64-pc-windows-msvc",
+ "manylinux2014_x86_64": "x86_64-unknown-linux-gnu",
+ "manylinux2014_aarch64": "aarch64-unknown-linux-gnu",
+ "macosx_11_0_arm64": "aarch64-apple-darwin",
+ "macosx_10_9_x86_64": "x86_64-apple-darwin",
+}
+
+
+@dataclass
+class Dep:
+ name: str
+ platform: str
+ python: str
+ url: str
+ sha256: str
+
+ @property
+ def repo_name(self):
+ return f"pypi__{self.name}_{self.python}_{self.platform}"
+
+ def __repr__(self):
+ return "\n".join(
+ [
+ "(",
+ f' "{self.url}",',
+ f' "{self.sha256}",',
+ ")",
+ ]
+ )
+
+
+@dataclass
+class Deps:
+ deps: list[Dep]
+
+ def __repr__(self):
+ deps = defaultdict(dict)
+ for d in self.deps:
+ deps[d.python][d.platform] = d
+
+ parts = []
+ for python, contents in deps.items():
+ inner = textwrap.indent(
+ "\n".join([f'"{platform}": {d},' for platform, d in contents.items()]),
+ prefix=" ",
+ )
+ parts.append('"{}": {{\n{}\n}},'.format(python, inner))
+ return "{{\n{}\n}}".format(textwrap.indent("\n".join(parts), prefix=" "))
+
+
+def _get_platforms(filename: str, name: str, version: str, python_version: str):
+ return filename[
+ len(f"{name}-{version}-{python_version}-{python_version}-") : -len(".whl")
+ ].split(".")
+
+
+def _map(
+ name: str,
+ filename: str,
+ python_version: str,
+ url: str,
+ digests: list,
+ platform: str,
+ **kwargs: Any,
+):
+ if platform not in _supported_platforms:
+ return None
+
+ return Dep(
+ name=name,
+ platform=_supported_platforms[platform],
+ python=python_version,
+ url=url,
+ sha256=digests["sha256"],
+ )
+
+
+def _writelines(path: pathlib.Path, lines: list[str]):
+ with open(path, "w") as f:
+ f.writelines(lines)
+
+
+def _difflines(path: pathlib.Path, lines: list[str]):
+ with open(path) as f:
+ input = f.readlines()
+
+ rules_python = pathlib.Path(__file__).parent.parent
+ p = path.relative_to(rules_python)
+
+ print(f"Diff of the changes that would be made to '{p}':")
+ for line in difflib.unified_diff(
+ input,
+ lines,
+ fromfile=f"a/{p}",
+ tofile=f"b/{p}",
+ ):
+ print(line, end="")
+
+ # Add an empty line at the end of the diff
+ print()
+
+
+def _update_file(
+ path: pathlib.Path,
+ snippet: str,
+ start_marker: str,
+ end_marker: str,
+ dry_run: bool = True,
+):
+ with open(path) as f:
+ input = f.readlines()
+
+ out = []
+ skip = False
+ for line in input:
+ if skip:
+ if not line.startswith(end_marker):
+ continue
+
+ skip = False
+
+ out.append(line)
+
+ if not line.startswith(start_marker):
+ continue
+
+ skip = True
+ out.extend([f"{line}\n" for line in snippet.splitlines()])
+
+ if dry_run:
+ _difflines(path, out)
+ else:
+ _writelines(path, out)
+
+
+def _parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(__doc__)
+ parser.add_argument(
+ "--name",
+ default="coverage",
+ type=str,
+ help="The name of the package",
+ )
+ parser.add_argument(
+ "version",
+ type=str,
+ help="The version of the package to download",
+ )
+ parser.add_argument(
+ "--py",
+ nargs="+",
+ type=str,
+ default=["cp38", "cp39", "cp310", "cp311"],
+ help="Supported python versions",
+ )
+ parser.add_argument(
+ "--dry-run",
+ action="store_true",
+ help="Wether to write to files",
+ )
+ return parser.parse_args()
+
+
+def main():
+ args = _parse_args()
+
+ api_url = f"https://pypi.python.org/pypi/{args.name}/{args.version}/json"
+ req = request.Request(api_url)
+ with request.urlopen(req) as response:
+ data = json.loads(response.read().decode("utf-8"))
+
+ urls = []
+ for u in data["urls"]:
+ if u["yanked"]:
+ continue
+
+ if not u["filename"].endswith(".whl"):
+ continue
+
+ if u["python_version"] not in args.py:
+ continue
+
+ if f'_{u["python_version"]}m_' in u["filename"]:
+ continue
+
+ platforms = _get_platforms(
+ u["filename"],
+ args.name,
+ args.version,
+ u["python_version"],
+ )
+
+ result = [_map(name=args.name, platform=p, **u) for p in platforms]
+ urls.extend(filter(None, result))
+
+ urls.sort(key=lambda x: f"{x.python}_{x.platform}")
+
+ rules_python = pathlib.Path(__file__).parent.parent
+
+ # Update the coverage_deps, which are used to register deps
+ _update_file(
+ path=rules_python / "python" / "private" / "coverage_deps.bzl",
+ snippet=f"_coverage_deps = {repr(Deps(urls))}\n",
+ start_marker="#START: managed by update_coverage_deps.py script",
+ end_marker="#END: managed by update_coverage_deps.py script",
+ dry_run=args.dry_run,
+ )
+
+ return
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/wheelmaker.py b/tools/wheelmaker.py
new file mode 100644
index 0000000..63b833f
--- /dev/null
+++ b/tools/wheelmaker.py
@@ -0,0 +1,436 @@
+# 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 argparse
+import base64
+import collections
+import hashlib
+import os
+import re
+import sys
+import zipfile
+from pathlib import Path
+
+
+def commonpath(path1, path2):
+ ret = []
+ for a, b in zip(path1.split(os.path.sep), path2.split(os.path.sep)):
+ if a != b:
+ break
+ ret.append(a)
+ return os.path.sep.join(ret)
+
+
+def escape_filename_segment(segment):
+ """Escapes a filename segment per https://www.python.org/dev/peps/pep-0427/#escaping-and-unicode"""
+ return re.sub(r"[^\w\d.]+", "_", segment, re.UNICODE)
+
+
+class WheelMaker(object):
+ def __init__(
+ self,
+ name,
+ version,
+ build_tag,
+ python_tag,
+ abi,
+ platform,
+ outfile=None,
+ strip_path_prefixes=None,
+ ):
+ self._name = name
+ self._version = version
+ self._build_tag = build_tag
+ self._python_tag = python_tag
+ self._abi = abi
+ self._platform = platform
+ self._outfile = outfile
+ self._strip_path_prefixes = (
+ strip_path_prefixes if strip_path_prefixes is not None else []
+ )
+
+ self._distinfo_dir = (
+ escape_filename_segment(self._name)
+ + "-"
+ + escape_filename_segment(self._version)
+ + ".dist-info/"
+ )
+ self._zipfile = None
+ # Entries for the RECORD file as (filename, hash, size) tuples.
+ self._record = []
+
+ def __enter__(self):
+ self._zipfile = zipfile.ZipFile(
+ self.filename(), mode="w", compression=zipfile.ZIP_DEFLATED
+ )
+ return self
+
+ def __exit__(self, type, value, traceback):
+ self._zipfile.close()
+ self._zipfile = None
+
+ def wheelname(self) -> str:
+ components = [self._name, self._version]
+ if self._build_tag:
+ components.append(self._build_tag)
+ components += [self._python_tag, self._abi, self._platform]
+ return "-".join(components) + ".whl"
+
+ def filename(self) -> str:
+ if self._outfile:
+ return self._outfile
+ return self.wheelname()
+
+ def disttags(self):
+ return ["-".join([self._python_tag, self._abi, self._platform])]
+
+ def distinfo_path(self, basename):
+ return self._distinfo_dir + basename
+
+ def _serialize_digest(self, hash):
+ # https://www.python.org/dev/peps/pep-0376/#record
+ # "base64.urlsafe_b64encode(digest) with trailing = removed"
+ digest = base64.urlsafe_b64encode(hash.digest())
+ digest = b"sha256=" + digest.rstrip(b"=")
+ return digest
+
+ def add_string(self, filename, contents):
+ """Add given 'contents' as filename to the distribution."""
+ if sys.version_info[0] > 2 and isinstance(contents, str):
+ contents = contents.encode("utf-8", "surrogateescape")
+ self._zipfile.writestr(filename, contents)
+ hash = hashlib.sha256()
+ hash.update(contents)
+ self._add_to_record(filename, self._serialize_digest(hash), len(contents))
+
+ def add_file(self, package_filename, real_filename):
+ """Add given file to the distribution."""
+
+ def arcname_from(name):
+ # Always use unix path separators.
+ normalized_arcname = name.replace(os.path.sep, "/")
+ # Don't manipulate names filenames in the .distinfo directory.
+ if normalized_arcname.startswith(self._distinfo_dir):
+ return normalized_arcname
+ for prefix in self._strip_path_prefixes:
+ if normalized_arcname.startswith(prefix):
+ return normalized_arcname[len(prefix) :]
+
+ return normalized_arcname
+
+ if os.path.isdir(real_filename):
+ directory_contents = os.listdir(real_filename)
+ for file_ in directory_contents:
+ self.add_file(
+ "{}/{}".format(package_filename, file_),
+ "{}/{}".format(real_filename, file_),
+ )
+ return
+
+ arcname = arcname_from(package_filename)
+
+ self._zipfile.write(real_filename, arcname=arcname)
+ # Find the hash and length
+ hash = hashlib.sha256()
+ size = 0
+ with open(real_filename, "rb") as f:
+ while True:
+ block = f.read(2**20)
+ if not block:
+ break
+ hash.update(block)
+ size += len(block)
+ self._add_to_record(arcname, self._serialize_digest(hash), size)
+
+ def add_wheelfile(self):
+ """Write WHEEL file to the distribution"""
+ # TODO(pstradomski): Support non-purelib wheels.
+ wheel_contents = """\
+Wheel-Version: 1.0
+Generator: bazel-wheelmaker 1.0
+Root-Is-Purelib: {}
+""".format(
+ "true" if self._platform == "any" else "false"
+ )
+ for tag in self.disttags():
+ wheel_contents += "Tag: %s\n" % tag
+ self.add_string(self.distinfo_path("WHEEL"), wheel_contents)
+
+ def add_metadata(self, metadata, name, description, version):
+ """Write METADATA file to the distribution."""
+ # https://www.python.org/dev/peps/pep-0566/
+ # https://packaging.python.org/specifications/core-metadata/
+ metadata = re.sub("^Name: .*$", "Name: %s" % name, metadata, flags=re.MULTILINE)
+ metadata += "Version: %s\n\n" % version
+ # setuptools seems to insert UNKNOWN as description when none is
+ # provided.
+ metadata += description if description else "UNKNOWN"
+ metadata += "\n"
+ self.add_string(self.distinfo_path("METADATA"), metadata)
+
+ def add_recordfile(self):
+ """Write RECORD file to the distribution."""
+ record_path = self.distinfo_path("RECORD")
+ entries = self._record + [(record_path, b"", b"")]
+ entries.sort()
+ contents = b""
+ for filename, digest, size in entries:
+ if sys.version_info[0] > 2 and isinstance(filename, str):
+ filename = filename.lstrip("/").encode("utf-8", "surrogateescape")
+ contents += b"%s,%s,%s\n" % (filename, digest, size)
+ self.add_string(record_path, contents)
+
+ def _add_to_record(self, filename, hash, size):
+ size = str(size).encode("ascii")
+ self._record.append((filename, hash, size))
+
+
+def get_files_to_package(input_files):
+ """Find files to be added to the distribution.
+
+ input_files: list of pairs (package_path, real_path)
+ """
+ files = {}
+ for package_path, real_path in input_files:
+ files[package_path] = real_path
+ return files
+
+
+def resolve_argument_stamp(
+ argument: str, volatile_status_stamp: Path, stable_status_stamp: Path
+) -> str:
+ """Resolve workspace status stamps format strings found in the argument string
+
+ Args:
+ argument (str): The raw argument represenation for the wheel (may include stamp variables)
+ volatile_status_stamp (Path): The path to a volatile workspace status file
+ stable_status_stamp (Path): The path to a stable workspace status file
+
+ Returns:
+ str: A resolved argument string
+ """
+ lines = (
+ volatile_status_stamp.read_text().splitlines()
+ + stable_status_stamp.read_text().splitlines()
+ )
+ for line in lines:
+ if not line:
+ continue
+ key, value = line.split(" ", maxsplit=1)
+ stamp = "{" + key + "}"
+ argument = argument.replace(stamp, value)
+
+ return argument
+
+
+def parse_args() -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description="Builds a python wheel")
+ metadata_group = parser.add_argument_group("Wheel name, version and platform")
+ metadata_group.add_argument(
+ "--name", required=True, type=str, help="Name of the distribution"
+ )
+ metadata_group.add_argument(
+ "--version", required=True, type=str, help="Version of the distribution"
+ )
+ metadata_group.add_argument(
+ "--build_tag",
+ type=str,
+ default="",
+ help="Optional build tag for the distribution",
+ )
+ metadata_group.add_argument(
+ "--python_tag",
+ type=str,
+ default="py3",
+ help="Python version, e.g. 'py2' or 'py3'",
+ )
+ metadata_group.add_argument("--abi", type=str, default="none")
+ metadata_group.add_argument(
+ "--platform", type=str, default="any", help="Target platform. "
+ )
+
+ output_group = parser.add_argument_group("Output file location")
+ output_group.add_argument(
+ "--out", type=str, default=None, help="Override name of ouptut file"
+ )
+ output_group.add_argument(
+ "--name_file",
+ type=Path,
+ help="A file where the canonical name of the " "wheel will be written",
+ )
+
+ output_group.add_argument(
+ "--strip_path_prefix",
+ type=str,
+ action="append",
+ default=[],
+ help="Path prefix to be stripped from input package files' path. "
+ "Can be supplied multiple times. Evaluated in order.",
+ )
+
+ wheel_group = parser.add_argument_group("Wheel metadata")
+ wheel_group.add_argument(
+ "--metadata_file",
+ type=Path,
+ help="Contents of the METADATA file (before appending contents of "
+ "--description_file)",
+ )
+ wheel_group.add_argument(
+ "--description_file", help="Path to the file with package description"
+ )
+ wheel_group.add_argument(
+ "--description_content_type", help="Content type of the package description"
+ )
+ wheel_group.add_argument(
+ "--entry_points_file",
+ help="Path to a correctly-formatted entry_points.txt file",
+ )
+
+ contents_group = parser.add_argument_group("Wheel contents")
+ contents_group.add_argument(
+ "--input_file",
+ action="append",
+ help="'package_path;real_path' pairs listing "
+ "files to be included in the wheel. "
+ "Can be supplied multiple times.",
+ )
+ contents_group.add_argument(
+ "--input_file_list",
+ action="append",
+ help="A file that has all the input files defined as a list to avoid "
+ "the long command",
+ )
+ contents_group.add_argument(
+ "--extra_distinfo_file",
+ action="append",
+ help="'filename;real_path' pairs listing extra files to include in"
+ "dist-info directory. Can be supplied multiple times.",
+ )
+
+ build_group = parser.add_argument_group("Building requirements")
+ build_group.add_argument(
+ "--volatile_status_file",
+ type=Path,
+ help="Pass in the stamp info file for stamping",
+ )
+ build_group.add_argument(
+ "--stable_status_file",
+ type=Path,
+ help="Pass in the stamp info file for stamping",
+ )
+
+ return parser.parse_args(sys.argv[1:])
+
+
+def main() -> None:
+ arguments = parse_args()
+
+ if arguments.input_file:
+ input_files = [i.split(";") for i in arguments.input_file]
+ else:
+ input_files = []
+
+ if arguments.extra_distinfo_file:
+ extra_distinfo_file = [i.split(";") for i in arguments.extra_distinfo_file]
+ else:
+ extra_distinfo_file = []
+
+ if arguments.input_file_list:
+ for input_file in arguments.input_file_list:
+ with open(input_file) as _file:
+ input_file_list = _file.read().splitlines()
+ for _input_file in input_file_list:
+ input_files.append(_input_file.split(";"))
+
+ all_files = get_files_to_package(input_files)
+ # Sort the files for reproducible order in the archive.
+ all_files = sorted(all_files.items())
+
+ strip_prefixes = [p for p in arguments.strip_path_prefix]
+
+ if arguments.volatile_status_file and arguments.stable_status_file:
+ name = resolve_argument_stamp(
+ arguments.name,
+ arguments.volatile_status_file,
+ arguments.stable_status_file,
+ )
+ else:
+ name = arguments.name
+
+ if arguments.volatile_status_file and arguments.stable_status_file:
+ version = resolve_argument_stamp(
+ arguments.version,
+ arguments.volatile_status_file,
+ arguments.stable_status_file,
+ )
+ else:
+ version = arguments.version
+
+ with WheelMaker(
+ name=name,
+ version=version,
+ build_tag=arguments.build_tag,
+ python_tag=arguments.python_tag,
+ abi=arguments.abi,
+ platform=arguments.platform,
+ outfile=arguments.out,
+ strip_path_prefixes=strip_prefixes,
+ ) as maker:
+ for package_filename, real_filename in all_files:
+ maker.add_file(package_filename, real_filename)
+ maker.add_wheelfile()
+
+ description = None
+ if arguments.description_file:
+ if sys.version_info[0] == 2:
+ with open(arguments.description_file, "rt") as description_file:
+ description = description_file.read()
+ else:
+ with open(
+ arguments.description_file, "rt", encoding="utf-8"
+ ) as description_file:
+ description = description_file.read()
+
+ metadata = None
+ if sys.version_info[0] == 2:
+ with open(arguments.metadata_file, "rt") as metadata_file:
+ metadata = metadata_file.read()
+ else:
+ with open(arguments.metadata_file, "rt", encoding="utf-8") as metadata_file:
+ metadata = metadata_file.read()
+
+ maker.add_metadata(
+ metadata=metadata, name=name, description=description, version=version
+ )
+
+ if arguments.entry_points_file:
+ maker.add_file(
+ maker.distinfo_path("entry_points.txt"), arguments.entry_points_file
+ )
+
+ # Sort the files for reproducible order in the archive.
+ for filename, real_path in sorted(extra_distinfo_file):
+ maker.add_file(maker.distinfo_path(filename), real_path)
+
+ maker.add_recordfile()
+
+ # Since stamping may otherwise change the target name of the
+ # wheel, the canonical name (with stamps resolved) is written
+ # to a file so consumers of the wheel can easily determine
+ # the correct name.
+ arguments.name_file.write_text(maker.wheelname())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/version.bzl b/version.bzl
new file mode 100644
index 0000000..8c7f01c
--- /dev/null
+++ b/version.bzl
@@ -0,0 +1,39 @@
+# Copyright 2019 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.
+"""Versions of rules_python dependencies."""
+
+# Currently used Bazel version. This version is what the rules here are tested
+# against.
+# This version should be updated together with the version of Bazel
+# in .bazelversion.
+BAZEL_VERSION = "6.0.0"
+
+# NOTE: Keep in sync with .bazelci/presubmit.yml
+# This is the minimum supported bazel version, that we have some tests for.
+MINIMUM_BAZEL_VERSION = "5.4.0"
+
+# Versions of Bazel which users should be able to use.
+# Ensures we don't break backwards-compatibility,
+# accidentally forcing users to update their LTS-supported bazel.
+# These are the versions used when testing nested workspaces with
+# bazel_integration_test.
+SUPPORTED_BAZEL_VERSIONS = [
+ BAZEL_VERSION,
+ # TODO @aignas 2023-02-15: the integration tests currently support
+ # only a single element in this array.
+ #MINIMUM_BAZEL_VERSION,
+]
+
+def bazel_version_to_binary_label(version):
+ return "@build_bazel_bazel_%s//:bazel_binary" % version.replace(".", "_")