diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 1da4d9fb6b..c9b8bc286d 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -93,6 +93,36 @@ tasks: <<: *reusable_config name: Default test on Ubuntu platform: ubuntu2004 + ubuntu_bazel_rolling: + <<: *reusable_config + name: "Default test: Ubuntu, Pystar, workspace" + platform: ubuntu2004 + # TODO: Change to "rolling" once + # https://github.com/bazelbuild/bazel/commit/f3aafea59ae021c6a12086cb2cd34c5fa782faf1 + # is available in rolling. + bazel: "last_green" + environment: + RULES_PYTHON_ENABLE_PYSTAR: "1" + test_flags: + # The doc check tests fail because the Starlark implementation makes the + # PyInfo and PyRuntimeInfo symbols become documented. + - "--test_tag_filters=-integration-test,-doc_check_test" + ubuntu_bazel_rolling_bzlmod: + <<: *reusable_config + <<: *common_bzlmod_flags + name: "Default test: Ubuntu, Pystar, bzlmod" + platform: ubuntu2004 + # TODO: Change to "rolling" once + # https://github.com/bazelbuild/bazel/commit/f3aafea59ae021c6a12086cb2cd34c5fa782faf1 + # is available in rolling. + bazel: "last_green" + environment: + RULES_PYTHON_ENABLE_PYSTAR: "1" + test_flags: + # The doc check tests fail because the Starlark implementation makes the + # PyInfo and PyRuntimeInfo symbols become documented. + - "--test_tag_filters=-integration-test,-doc_check_test" + debian: <<: *reusable_config name: Default test on Debian @@ -107,7 +137,6 @@ tasks: platform: windows test_flags: - "--test_tag_filters=-integration-test,-fix-windows" - rbe_min: <<: *minimum_supported_version <<: *reusable_config @@ -262,34 +291,6 @@ tasks: 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 diff --git a/.bazelignore b/.bazelignore index 135f709824..564eb06195 100644 --- a/.bazelignore +++ b/.bazelignore @@ -6,6 +6,13 @@ bazel-rules_python bazel-bin bazel-out bazel-testlogs +# Prevent the convenience symlinks within the examples from being +# treated as directories with valid BUILD files for the main repo. +# Any directory with a WORKSPACE in it should be added here, with +# an entry like `bazel-{workspacename}` examples/bzlmod/bazel-bzlmod +examples/bzlmod/other_module/bazel-other_module examples/bzlmod_build_file_generation/bazel-bzlmod_build_file_generation +examples/pip_parse/bazel-pip_parse examples/py_proto_library/bazel-py_proto_library +tests/ignore_root_user_error/bazel-ignore_root_user_error diff --git a/.bazelrc b/.bazelrc index 3a5497a071..39b28d12e6 100644 --- a/.bazelrc +++ b/.bazelrc @@ -3,8 +3,8 @@ # 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_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_point,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,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_build_file_generation,examples/bzlmod_build_file_generation/other_module/other_module/pkg,examples/bzlmod_build_file_generation/runfiles,examples/bzlmod/entry_point,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,examples/bzlmod/whl_mods,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 +build --deleted_packages=examples/build_file_generation,examples/build_file_generation/random_number_generator,examples/bzlmod,examples/bzlmod/entry_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,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_points,examples/bzlmod/entry_points/tests,examples/bzlmod/libs/my_lib,examples/bzlmod/other_module,examples/bzlmod/other_module/other_module/pkg,examples/bzlmod/runfiles,examples/bzlmod/tests,examples/bzlmod/tests/other_module,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 diff --git a/.bcr/config.yml b/.bcr/config.yml index 7bdd70fbaf..7672aa554d 100644 --- a/.bcr/config.yml +++ b/.bcr/config.yml @@ -14,5 +14,5 @@ fixedReleaser: login: f0rmiga - email: thulio@aspect.dev + email: 3149049+f0rmiga@users.noreply.github.com moduleRoots: [".", "gazelle"] diff --git a/BUILD.bazel b/BUILD.bazel index 35a3df892f..4d4d3ec26f 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@bazel_skylib//:bzl_library.bzl", "bzl_library") load(":version.bzl", "BAZEL_VERSION") package(default_visibility = ["//visibility:public"]) @@ -44,6 +45,12 @@ filegroup( ], ) +bzl_library( + name = "version_bzl", + srcs = ["version.bzl"], + visibility = ["//:__subpackages__"], +) + # Reexport of all bzl files used to allow downstream rules to generate docs # without shipping with a dependency on Skylib filegroup( diff --git a/CHANGELOG.md b/CHANGELOG.md index 502545adec..512d820d23 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,10 +17,87 @@ A brief description of the categories of changes: * Particular sub-systems are identified using parentheses, e.g. `(bzlmod)` or `(docs)`. +## Unreleased + +### Changed + +* Python version patch level bumps: + * 3.8.15 -> 3.8.18 + * 3.9.17 -> 3.9.18 + * 3.10.12 -> 3.10.13 + * 3.11.4 -> 3.11.6 + +* (deps) Upgrade rules_go 0.39.1 -> 0.41.0; this is so gazelle integration works with upcoming Bazel versions + +* (multi-version) The `distribs` attribute is no longer propagated. This + attribute has been long deprecated by Bazel and shouldn't be used. + +* Calling `//python:repositories.bzl#py_repositories()` is required. It has + always been documented as necessary, but it was possible to omit it in certain + cases. An error about `@rules_python_internal` means the `py_repositories()` + call is missing in `WORKSPACE`. + +* (bzlmod) The `pip.parse` extension will generate os/arch specific lock + file entries on `bazel>=6.4`. + + +### Added + +* (bzlmod, entry_point) Added + [`py_console_script_binary`](./docs/py_console_script_binary.md), which + allows adding custom dependencies to a package's entry points and customizing + the `py_binary` rule used to build it. + +* New Python versions available: `3.8.17`, `3.11.5` using + https://github.com/indygreg/python-build-standalone/releases/tag/20230826. + +* (gazelle) New `# gazelle:python_generation_mode file` directive to support + generating one `py_library` per file. + +* (python_repository) Support `netrc` and `auth_patterns` attributes to enable + authentication against private HTTP hosts serving Python toolchain binaries. + +* `//python:packaging_bzl` added, a `bzl_library` for the Starlark + files `//python:packaging.bzl` requires. +* (py_wheel) Added the `incompatible_normalize_name` feature flag to + normalize the package distribution name according to latest Python + packaging standards. Defaults to `False` for the time being. +* (py_wheel) Added the `incompatible_normalize_version` feature flag + to normalize the package version according to PEP440 standard. This + also adds support for local version specifiers (versions with a `+` + in them), in accordance with PEP440. Defaults to `False` for the + time being. + +* New Python versions available: `3.8.18`, `3.9.18`, `3.10.13`, `3.11.6`, `3.12.0` using + https://github.com/indygreg/python-build-standalone/releases/tag/20231002. + `3.12.0` support is considered beta and may have issues. + +### Removed + +* (bzlmod) The `entry_point` macro is no longer supported and has been removed + in favour of the `py_console_script_binary` macro for `bzlmod` users. + +* (bzlmod) The `pip.parse` no longer generates `{hub_name}_{py_version}` hub repos + as the `entry_point` macro has been superseded by `py_console_script_binary`. + +* (bzlmod) The `pip.parse` no longer generates `{hub_name}_{distribution}` hub repos. + +### Fixed + +* (whl_library) No longer restarts repository rule when fetching external + dependencies improving initial build times involving external dependency + fetching. + +* (gazelle) Improve runfiles lookup hermeticity. + ## [0.25.0] - 2023-08-22 ### Changed +* Python version patch level bumps: + * 3.9.16 -> 3.9.17 + * 3.10.9 -> 3.10.12 + * 3.11.1 -> 3.11.4 * (bzlmod) `pip.parse` can no longer automatically use the default Python version; this was an unreliable and unsafe behavior. The `python_version` arg must always be explicitly specified. @@ -76,5 +153,3 @@ A brief description of the categories of changes: * Expose Python C headers through the toolchain. [0.24.0]: https://github.com/bazelbuild/rules_python/releases/tag/0.24.0 - - diff --git a/MODULE.bazel b/MODULE.bazel index aaa5c86912..e9b06d66ef 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -4,8 +4,9 @@ module( compatibility_level = 1, ) -bazel_dep(name = "platforms", version = "0.0.4") +bazel_dep(name = "bazel_features", version = "1.1.0") bazel_dep(name = "bazel_skylib", version = "1.3.0") +bazel_dep(name = "platforms", version = "0.0.4") # Those are loaded only when using py_proto_library bazel_dep(name = "rules_proto", version = "5.3.0-21.7") @@ -15,6 +16,7 @@ internal_deps = use_extension("@rules_python//python/extensions/private:internal internal_deps.install() use_repo( internal_deps, + "rules_python_internal", # START: maintained by 'bazel run //tools/private:update_pip_deps' "pypi__build", "pypi__click", @@ -26,6 +28,7 @@ use_repo( "pypi__pep517", "pypi__pip", "pypi__pip_tools", + "pypi__pyproject_hooks", "pypi__setuptools", "pypi__tomli", "pypi__wheel", diff --git a/README.md b/README.md index 660e6e20af..10c7d0a4be 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Python Rules for Bazel -[](https://buildkite.com/bazel/python-rules-python-postsubmit) +[](https://buildkite.com/bazel/rules-python-python) ## Overview diff --git a/WORKSPACE b/WORKSPACE index 7438bb8257..ad32013816 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -30,11 +30,11 @@ load("//python:versions.bzl", "MINOR_MAPPING") python_register_multi_toolchains( name = "python", - default_version = MINOR_MAPPING.values()[-1], + default_version = MINOR_MAPPING.values()[-2], python_versions = MINOR_MAPPING.values(), ) -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive", "http_file") # Used for Bazel CI http_archive( @@ -72,7 +72,7 @@ _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.4:defs.bzl", "interpreter") +load("@python//3.11.6:defs.bzl", "interpreter") load("@rules_python//python:pip.bzl", "pip_parse") pip_parse( @@ -86,3 +86,14 @@ pip_parse( load("@publish_deps//:requirements.bzl", "install_deps") install_deps() + +# This wheel is purely here to validate the wheel extraction code. It's not +# intended for anything else. +http_file( + name = "wheel_for_testing", + downloaded_file_path = "numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + sha256 = "0d60fbae8e0019865fc4784745814cff1c421df5afee233db6d88ab4f14655a2", + urls = [ + "https://files.pythonhosted.org/packages/50/67/3e966d99a07d60a21a21d7ec016e9e4c2642a86fea251ec68677daf71d4d/numpy-1.25.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", + ], +) diff --git a/docs/BUILD.bazel b/docs/BUILD.bazel index 1fb4f81484..0959c35ddd 100644 --- a/docs/BUILD.bazel +++ b/docs/BUILD.bazel @@ -16,7 +16,10 @@ 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") +load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED") # buildifier: disable=bzl-visibility +# NOTE: Only public visibility for historical reasons. +# This package is only for rules_python to generate its own docs. package(default_visibility = ["//visibility:public"]) licenses(["notice"]) # Apache 2.0 @@ -27,71 +30,51 @@ _DOCS = { "pip_repository": "//docs:pip-repository", "py_cc_toolchain": "//docs:py_cc_toolchain-docs", "py_cc_toolchain_info": "//docs:py_cc_toolchain_info-docs", + "py_console_script_binary": "//docs:py-console-script-binary", "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", - ], +# Temporary compatibility aliases for some other projects depending on the old +# bzl_library targets. +alias( + name = "defs", + actual = "//python:defs_bzl", + deprecation = "Use //python:defs_bzl instead; targets under //docs are internal.", ) -bzl_library( - name = "defs", - srcs = [ - "//python:defs.bzl", - "//python/private:reexports.bzl", - ], - deps = [ - ":bazel_repo_tools", - "//python:defs_bzl", - "//python/private:reexports_bzl", - ], +alias( + name = "bazel_repo_tools", + actual = "//python/private:bazel_tools_bzl", + deprecation = "Use @bazel_tools//tools:bzl_srcs instead; targets under //docs are internal.", ) bzl_library( name = "pip_install_bzl", - srcs = [ - "//python:bzl", - "//python/pip_install:bzl", - ], + deprecation = "Use //python:pip_bzl or //python/pip_install:pip_repository_bzl instead; " + + "targets under //docs are internal.", deps = [ - ":defs", - "//:version.bzl", + "//python:pip_bzl", + "//python/pip_install:pip_repository_bzl", ], ) -bzl_library( +alias( name = "requirements_parser_bzl", - srcs = [ - "//python/pip_install:requirements_parser.bzl", - ], + actual = "//python/pip_install:pip_repository_bzl", + deprecation = "Use //python/pip_install:pip_repository_bzl instead; Both the requirements " + + "parser and targets under //docs are internal", ) -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", - ], -) +# Empty list means "compatible with all". +# Stardoc+bzlmod doesn't currently work with our docs, so disable trying to +# build it for now. +_COMPATIBLE_PLATFORM = [] if not BZLMOD_ENABLED else ["@platforms//:incompatible"] # 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": [], +_TARGET_COMPATIBLE_WITH = select({ + "@platforms//os:linux": _COMPATIBLE_PLATFORM, + "@platforms//os:macos": _COMPATIBLE_PLATFORM, "//conditions:default": ["@platforms//:incompatible"], }) @@ -99,19 +82,19 @@ stardoc( name = "core-docs", out = "python.md_", input = "//python:defs.bzl", - target_compatible_with = _NOT_WINDOWS, - deps = [":defs"], + target_compatible_with = _TARGET_COMPATIBLE_WITH, + deps = [ + "//python:defs_bzl", + ], ) stardoc( name = "pip-docs", out = "pip.md_", input = "//python:pip.bzl", - target_compatible_with = _NOT_WINDOWS, + target_compatible_with = _TARGET_COMPATIBLE_WITH, deps = [ - ":bazel_repo_tools", - ":pip_install_bzl", - "@bazel_skylib//lib:versions", + "//python:pip_bzl", ], ) @@ -119,12 +102,19 @@ stardoc( name = "pip-repository", out = "pip_repository.md_", input = "//python/pip_install:pip_repository.bzl", - target_compatible_with = _NOT_WINDOWS, + target_compatible_with = _TARGET_COMPATIBLE_WITH, + deps = [ + "//python/pip_install:pip_repository_bzl", + ], +) + +stardoc( + name = "py-console-script-binary", + out = "py_console_script_binary.md_", + input = "//python/entry_points:py_console_script_binary.bzl", + target_compatible_with = _TARGET_COMPATIBLE_WITH, deps = [ - ":bazel_repo_tools", - ":pip_install_bzl", - ":requirements_parser_bzl", - "@bazel_skylib//lib:versions", + "//python/entry_points:py_console_script_binary_bzl", ], ) @@ -132,8 +122,8 @@ stardoc( name = "packaging-docs", out = "packaging.md_", input = "//python:packaging.bzl", - target_compatible_with = _NOT_WINDOWS, - deps = [":packaging_bzl"], + target_compatible_with = _TARGET_COMPATIBLE_WITH, + deps = ["//python:packaging_bzl"], ) stardoc( @@ -144,7 +134,7 @@ stardoc( # 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, + target_compatible_with = _TARGET_COMPATIBLE_WITH, deps = ["//python/private:py_cc_toolchain_bzl"], ) @@ -161,7 +151,8 @@ stardoc( failure_message = "Please run: bazel run //docs:update", file1 = k + ".md", file2 = k + ".md_", - target_compatible_with = _NOT_WINDOWS, + tags = ["doc_check_test"], + target_compatible_with = _TARGET_COMPATIBLE_WITH, ) for k in _DOCS.keys() ] @@ -176,12 +167,12 @@ write_file( "cp -fv bazel-bin/docs/{0}.md_ docs/{0}.md".format(k) for k in _DOCS.keys() ], - target_compatible_with = _NOT_WINDOWS, + target_compatible_with = _TARGET_COMPATIBLE_WITH, ) sh_binary( name = "update", srcs = ["update.sh"], data = _DOCS.values(), - target_compatible_with = _NOT_WINDOWS, + target_compatible_with = _TARGET_COMPATIBLE_WITH, ) diff --git a/docs/packaging.md b/docs/packaging.md index 0e8e110ef5..90c66dc1de 100755 --- a/docs/packaging.md +++ b/docs/packaging.md @@ -59,8 +59,9 @@ This also has the advantage that stamping information is included in the wheel's
py_wheel_rule(name, abi, author, author_email, classifiers, console_scripts, deps, description_content_type, description_file, distribution, entry_points, - extra_distinfo_files, extra_requires, homepage, license, platform, project_urls, - python_requires, python_tag, requires, stamp, strip_path_prefixes, summary, version) + extra_distinfo_files, extra_requires, homepage, incompatible_normalize_name, + incompatible_normalize_version, license, platform, project_urls, python_requires, + python_tag, requires, stamp, strip_path_prefixes, summary, version)Internal rule used by the [py_wheel macro](/docs/packaging.md#py_wheel). @@ -89,6 +90,8 @@ in the way they expect. | extra_distinfo_files | Extra files to add to distinfo directory in the archive. | Dictionary: Label -> String | optional |
{}
|
| extra_requires | List of optional requirements for this package | Dictionary: String -> List of strings | optional | {}
|
| homepage | A string specifying the URL for the package homepage. | String | optional | ""
|
+| incompatible_normalize_name | Normalize the package distribution name according to latest Python packaging standards.False
|
+| incompatible_normalize_version | Normalize the package version according to PEP440 standard. With this option set to True, if the user wants to pass any stamp variables, they have to be enclosed in '{}', e.g. '{BUILD_TIMESTAMP}'. | Boolean | optional | False
|
| license | A string specifying the license of the package. | String | optional | ""
|
| platform | Supported platform. Use 'any' for pure-Python wheel. platform = select({ "//platforms:windows_x86_64": "win_amd64", "//platforms:macos_x86_64": "macosx_10_7_x86_64", "//platforms:linux_x86_64": "manylinux2014_x86_64", })
| String | optional | "any"
|
| 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 {{"Bug Tracker": "http://bitbucket.org/tarek/distribute/issues/"}}
| Dictionary: String -> String | optional | {}
|
diff --git a/docs/pip_repository.md b/docs/pip_repository.md
index 853605276f..453ca29713 100644
--- a/docs/pip_repository.md
+++ b/docs/pip_repository.md
@@ -7,7 +7,7 @@
## pip_hub_repository_bzlmod
-pip_hub_repository_bzlmod(name, repo_mapping, repo_name, whl_library_alias_names) +pip_hub_repository_bzlmod(name, default_version, repo_mapping, repo_name, whl_map)A rule for bzlmod mulitple pip repository creation. PRIVATE USE ONLY. @@ -18,9 +18,10 @@ A rule for bzlmod mulitple pip repository creation. PRIVATE USE ONLY. | Name | Description | Type | Mandatory | Default | | :------------- | :------------- | :------------- | :------------- | :------------- | | name | A unique name for this repository. | Name | required | | +| default_version | This is the default python version in the format of X.Y.Z. This should match what is setup by the 'python' extension using the 'is_default = True' setting. | String | required | | | repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.<p>For example, an entry
"@foo": "@bar"
declares that, for any time this repository depends on @foo
(such as a dependency on @foo//some:target
, it should actually resolve that dependency within globally-declared @bar
(@bar//some:target
). | Dictionary: String -> String | required | |
| repo_name | The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name. | String | required | |
-| whl_library_alias_names | The list of whl alias that we use to build aliases and the whl names | List of strings | required | |
+| whl_map | The wheel map where values are python versions | Dictionary: String -> List of strings | required | |
@@ -101,31 +102,6 @@ py_binary(
| timeout | Timeout (in seconds) on the rule's execution duration. | Integer | optional | 600
|
-
-
-## pip_repository_bzlmod
-
--pip_repository_bzlmod(name, repo_mapping, repo_name, requirements_darwin, requirements_linux, - requirements_lock, requirements_windows) -- -A rule for bzlmod pip_repository creation. Intended for private use only. - -**ATTRIBUTES** - - -| Name | Description | Type | Mandatory | Default | -| :------------- | :------------- | :------------- | :------------- | :------------- | -| name | A unique name for this repository. | Name | required | | -| repo_mapping | A dictionary from local repository name to global repository name. This allows controls over workspace dependency resolution for dependencies of this repository.<p>For example, an entry
"@foo": "@bar"
declares that, for any time this repository depends on @foo
(such as a dependency on @foo//some:target
, it should actually resolve that dependency within globally-declared @bar
(@bar//some:target
). | Dictionary: String -> String | required | |
-| repo_name | The apparent name of the repo. This is needed because in bzlmod, the name attribute becomes the canonical name | String | required | |
-| requirements_darwin | Override the requirements_lock attribute when the host platform is Mac OS | Label | optional | None
|
-| requirements_linux | Override the requirements_lock attribute when the host platform is Linux | Label | optional | None
|
-| 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'. | Label | optional | None
|
-| requirements_windows | Override the requirements_lock attribute when the host platform is Windows | Label | optional | None
|
-
-
## whl_library
diff --git a/docs/py_console_script_binary.md b/docs/py_console_script_binary.md
new file mode 100644
index 0000000000..3d7b5e5bbd
--- /dev/null
+++ b/docs/py_console_script_binary.md
@@ -0,0 +1,87 @@
+
+
+
+Creates an executable (a non-test binary) for console_script entry points.
+
+Generate a `py_binary` target for a particular console_script `entry_point`
+from a PyPI package, e.g. for creating an executable `pylint` target use:
+```starlark
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "pylint",
+ pkg = "@pip//pylint",
+)
+```
+
+Or for more advanced setups you can also specify extra dependencies and the
+exact script name you want to call. It is useful for tools like flake8, pylint,
+pytest, which have plugin discovery methods and discover dependencies from the
+PyPI packages available in the PYTHONPATH.
+```starlark
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "pylint_with_deps",
+ pkg = "@pip//pylint",
+ # Because `pylint` has multiple console_scripts available, we have to
+ # specify which we want if the name of the target name 'pylint_with_deps'
+ # cannot be used to guess the entry_point script.
+ script = "pylint",
+ deps = [
+ # One can add extra dependencies to the entry point.
+ # This specifically allows us to add plugins to pylint.
+ "@pip//pylint_print",
+ ],
+)
+```
+
+A specific Python version can be forced by using the generated version-aware
+wrappers, e.g. to force Python 3.9:
+```starlark
+load("@python_versions//3.9:defs.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "yamllint",
+ pkg = "@pip//yamllint",
+)
+```
+
+Alternatively, the the `py_console_script_binary.binary_rule` arg can be passed
+the version-bound `py_binary` symbol, or any other `py_binary`-compatible rule
+of your choosing:
+```starlark
+load("@python_versions//3.9:defs.bzl", "py_binary")
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "yamllint",
+ pkg = "@pip//yamllint:pkg",
+ binary_rule = py_binary,
+)
+```
+
+
+
+
+## py_console_script_binary
+
++py_console_script_binary(name, pkg, entry_points_txt, script, binary_rule, kwargs) ++ +Generate a py_binary for a console_script entry_point. + +**PARAMETERS** + + +| Name | Description | Default Value | +| :------------- | :------------- | :------------- | +| name | str, The name of the resulting target. | none | +| pkg | target, the package for which to generate the script. | none | +| entry_points_txt | optional target, the entry_points.txt file to parse for available console_script values. It may be a single file, or a group of files, but must contain a file named
entry_points.txt
. If not specified, defaults to the dist_info
target in the same package as the pkg
Label. | None
|
+| script | str, The console script name that the py_binary is going to be generated for. Defaults to the normalized name attribute. | None
|
+| binary_rule | callable, The rule/macro to use to instantiate the target. It's expected to behave like py_binary
. Defaults to @rules_python//python:py_binary.bzl#py_binary. | <function py_binary>
|
+| kwargs | Extra parameters forwarded to binary_rule. | none |
+
+
diff --git a/examples/build_file_generation/BUILD.bazel b/examples/build_file_generation/BUILD.bazel
index 928fb128e2..79f62519df 100644
--- a/examples/build_file_generation/BUILD.bazel
+++ b/examples/build_file_generation/BUILD.bazel
@@ -12,7 +12,6 @@ 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",
diff --git a/examples/build_file_generation/WORKSPACE b/examples/build_file_generation/WORKSPACE
index 7c74835870..03085d86b5 100644
--- a/examples/build_file_generation/WORKSPACE
+++ b/examples/build_file_generation/WORKSPACE
@@ -20,10 +20,10 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_bazel_rules_go",
- sha256 = "6dc2da7ab4cf5d7bfc7c949776b1b7c733f05e56edc4bcd9022bb249d2e2a996",
+ sha256 = "278b7ff5a826f3dc10f04feaf0b70d48b68748ccd512d7f98bf442077f043fe3",
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",
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.41.0/rules_go-v0.41.0.zip",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.41.0/rules_go-v0.41.0.zip",
],
)
@@ -71,8 +71,11 @@ local_repository(
path = "../../gazelle",
)
-# Next we load the toolchain from rules_python.
-load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+# Next we load the setup and toolchain from rules_python.
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+# Perform general setup
+py_repositories()
# 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
diff --git a/examples/bzlmod/BUILD.bazel b/examples/bzlmod/BUILD.bazel
index 3db7751f72..ff14016b85 100644
--- a/examples/bzlmod/BUILD.bazel
+++ b/examples/bzlmod/BUILD.bazel
@@ -16,7 +16,6 @@ load("@rules_python//python:defs.bzl", "py_binary", "py_library", "py_test")
# 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",
@@ -26,7 +25,6 @@ compile_pip_requirements_3_9(
# 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",
diff --git a/examples/bzlmod/MODULE.bazel b/examples/bzlmod/MODULE.bazel
index be9466d883..0d1c7a736b 100644
--- a/examples/bzlmod/MODULE.bazel
+++ b/examples/bzlmod/MODULE.bazel
@@ -113,10 +113,7 @@ pip.parse(
"@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")
+use_repo(pip, "pip")
bazel_dep(name = "other_module", version = "", repo_name = "our_other_module")
local_path_override(
diff --git a/examples/bzlmod/entry_point/BUILD.bazel b/examples/bzlmod/entry_point/BUILD.bazel
deleted file mode 100644
index f68552c3ef..0000000000
--- a/examples/bzlmod/entry_point/BUILD.bazel
+++ /dev/null
@@ -1,20 +0,0 @@
-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_points/BUILD.bazel b/examples/bzlmod/entry_points/BUILD.bazel
new file mode 100644
index 0000000000..a0939cb65b
--- /dev/null
+++ b/examples/bzlmod/entry_points/BUILD.bazel
@@ -0,0 +1,33 @@
+load("@python_versions//3.9:defs.bzl", py_console_script_binary_3_9 = "py_console_script_binary")
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+# This is how you can define a `pylint` entrypoint which uses the default python version.
+py_console_script_binary(
+ name = "pylint",
+ pkg = "@pip//pylint",
+ visibility = ["//entry_points:__subpackages__"],
+)
+
+# We can also specify extra dependencies for the binary, which is useful for
+# tools like flake8, pylint, pytest, which have plugin discovery methods.
+py_console_script_binary(
+ name = "pylint_with_deps",
+ pkg = "@pip//pylint",
+ # Because `pylint` has multiple console_scripts available, we have to
+ # specify which we want if the name of the target name 'pylint_with_deps'
+ # cannot be used to guess the entry_point script.
+ script = "pylint",
+ visibility = ["//entry_points:__subpackages__"],
+ deps = [
+ # One can add extra dependencies to the entry point.
+ "@pip//pylint_print",
+ ],
+)
+
+# A specific Python version can be forced by using the generated version-aware
+# wrappers, e.g. to force Python 3.9:
+py_console_script_binary_3_9(
+ name = "yamllint",
+ pkg = "@pip//yamllint:pkg",
+ visibility = ["//entry_points:__subpackages__"],
+)
diff --git a/examples/bzlmod/entry_points/tests/BUILD.bazel b/examples/bzlmod/entry_points/tests/BUILD.bazel
new file mode 100644
index 0000000000..5a65e9e1a3
--- /dev/null
+++ b/examples/bzlmod/entry_points/tests/BUILD.bazel
@@ -0,0 +1,63 @@
+load("@bazel_skylib//rules:run_binary.bzl", "run_binary")
+load("@rules_python//python:defs.bzl", "py_test")
+
+# Below are targets for testing the `py_console_script_binary` feature and are
+# not part of the example how to use the feature.
+
+# And a test that we can correctly run `pylint --version`
+py_test(
+ name = "pylint_test",
+ srcs = ["pylint_test.py"],
+ data = ["//entry_points:pylint"],
+ env = {
+ "ENTRY_POINT": "$(rlocationpath //entry_points:pylint)",
+ },
+ deps = ["@rules_python//python/runfiles"],
+)
+
+# Next run pylint on the file to generate a report.
+run_binary(
+ name = "pylint_report",
+ srcs = [
+ ":file_with_pylint_errors.py",
+ ],
+ outs = ["pylint_report.txt"],
+ args = [
+ "--output-format=text:$(location pylint_report.txt)",
+ "--load-plugins=pylint_print",
+ # The `exit-zero` ensures that `run_binary` is successful even though there are lint errors.
+ # We check the generated report in the test below.
+ "--exit-zero",
+ "$(location :file_with_pylint_errors.py)",
+ ],
+ env = {
+ # otherwise it may try to create ${HOME}/.cache/pylint
+ "PYLINTHOME": "./.pylint_home",
+ },
+ tool = "//entry_points:pylint_with_deps",
+)
+
+py_test(
+ name = "pylint_deps_test",
+ srcs = ["pylint_deps_test.py"],
+ data = [
+ ":pylint_report",
+ "//entry_points:pylint_with_deps",
+ ],
+ env = {
+ "ENTRY_POINT": "$(rlocationpath //entry_points:pylint_with_deps)",
+ "PYLINT_REPORT": "$(rlocationpath :pylint_report)",
+ },
+ deps = ["@rules_python//python/runfiles"],
+)
+
+# And a test to check that yamllint works
+py_test(
+ name = "yamllint_test",
+ srcs = ["yamllint_test.py"],
+ data = ["//entry_points:yamllint"],
+ env = {
+ "ENTRY_POINT": "$(rlocationpath //entry_points:yamllint)",
+ },
+ deps = ["@rules_python//python/runfiles"],
+)
diff --git a/examples/bzlmod/entry_points/tests/file_with_pylint_errors.py b/examples/bzlmod/entry_points/tests/file_with_pylint_errors.py
new file mode 100644
index 0000000000..bb3dbab660
--- /dev/null
+++ b/examples/bzlmod/entry_points/tests/file_with_pylint_errors.py
@@ -0,0 +1,6 @@
+"""
+A file to demonstrate the pylint-print checker works.
+"""
+
+if __name__ == "__main__":
+ print("Hello, World!")
diff --git a/examples/bzlmod/entry_points/tests/pylint_deps_test.py b/examples/bzlmod/entry_points/tests/pylint_deps_test.py
new file mode 100644
index 0000000000..f6743ce9b5
--- /dev/null
+++ b/examples/bzlmod/entry_points/tests/pylint_deps_test.py
@@ -0,0 +1,72 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 tempfile
+import unittest
+
+from python.runfiles import runfiles
+
+
+class ExampleTest(unittest.TestCase):
+ def __init__(self, *args, **kwargs):
+ self.maxDiff = None
+
+ super().__init__(*args, **kwargs)
+
+ def test_pylint_entry_point(self):
+ rlocation_path = os.environ.get("ENTRY_POINT")
+ assert (
+ rlocation_path is not None
+ ), "expected '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")
+
+ # Let's run the entrypoint and check the tool version.
+ #
+ # NOTE @aignas 2023-08-24: the Windows python launcher with Python 3.9 and bazel 6 is not happy if we start
+ # passing extra files via `subprocess.run` and it starts to fail with an error that the file which is the
+ # entry_point cannot be found. However, just calling `--version` seems to be fine.
+ proc = subprocess.run(
+ [str(entry_point), "--version"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertEqual(
+ "",
+ proc.stderr.decode("utf-8").strip(),
+ )
+ self.assertRegex(proc.stdout.decode("utf-8").strip(), "^pylint 2\.15\.9")
+
+ def test_pylint_report_has_expected_warnings(self):
+ rlocation_path = os.environ.get("PYLINT_REPORT")
+ assert (
+ rlocation_path is not None
+ ), "expected 'PYLINT_REPORT' env variable to be set to rlocation of the report"
+
+ pylint_report = pathlib.Path(runfiles.Create().Rlocation(rlocation_path))
+ self.assertTrue(pylint_report.exists(), f"'{pylint_report}' does not exist")
+
+ self.assertRegex(
+ pylint_report.read_text().strip(),
+ "W8201: Logging should be used instead of the print\(\) function\. \(print-function\)",
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/bzlmod/entry_points/tests/pylint_test.py b/examples/bzlmod/entry_points/tests/pylint_test.py
new file mode 100644
index 0000000000..c2532938d8
--- /dev/null
+++ b/examples/bzlmod/entry_points/tests/pylint_test.py
@@ -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.
+
+import os
+import pathlib
+import subprocess
+import unittest
+
+from python.runfiles import runfiles
+
+
+class ExampleTest(unittest.TestCase):
+ def __init__(self, *args, **kwargs):
+ self.maxDiff = None
+
+ super().__init__(*args, **kwargs)
+
+ def test_pylint_entry_point(self):
+ rlocation_path = os.environ.get("ENTRY_POINT")
+ assert (
+ rlocation_path is not None
+ ), "expected '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")
+
+ # Let's run the entrypoint and check the tool version.
+ #
+ # NOTE @aignas 2023-08-24: the Windows python launcher with Python 3.9 and bazel 6 is not happy if we start
+ # passing extra files via `subprocess.run` and it starts to fail with an error that the file which is the
+ # entry_point cannot be found. However, just calling `--version` seems to be fine.
+ proc = subprocess.run(
+ [str(entry_point), "--version"],
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ self.assertEqual(
+ "",
+ proc.stderr.decode("utf-8").strip(),
+ )
+ self.assertRegex(proc.stdout.decode("utf-8").strip(), "^pylint 2\.15\.9")
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/examples/bzlmod/entry_point/test_entry_point.py b/examples/bzlmod/entry_points/tests/yamllint_test.py
similarity index 64%
rename from examples/bzlmod/entry_point/test_entry_point.py
rename to examples/bzlmod/entry_points/tests/yamllint_test.py
index 5a37458348..0a0235793b 100644
--- a/examples/bzlmod/entry_point/test_entry_point.py
+++ b/examples/bzlmod/entry_points/tests/yamllint_test.py
@@ -21,15 +21,25 @@
class ExampleTest(unittest.TestCase):
- def test_entry_point(self):
- rlocation_path = os.environ.get("YAMLLINT_ENTRY_POINT")
+ def __init__(self, *args, **kwargs):
+ self.maxDiff = None
+
+ super().__init__(*args, **kwargs)
+
+ def test_yamllint_entry_point(self):
+ rlocation_path = os.environ.get("ENTRY_POINT")
assert (
rlocation_path is not None
- ), "expected 'YAMLLINT_ENTRY_POINT' env variable to be set to rlocation of the tool"
+ ), "expected '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")
+ # Let's run the entrypoint and check the tool version.
+ #
+ # NOTE @aignas 2023-08-24: the Windows python launcher with Python 3.9 and bazel 6 is not happy if we start
+ # passing extra files via `subprocess.run` and it starts to fail with an error that the file which is the
+ # entry_point cannot be found. However, just calling `--version` seems to be fine.
proc = subprocess.run(
[str(entry_point), "--version"],
check=True,
diff --git a/examples/bzlmod/requirements.in b/examples/bzlmod/requirements.in
index 47cdcf1ea8..702e15103d 100644
--- a/examples/bzlmod/requirements.in
+++ b/examples/bzlmod/requirements.in
@@ -7,4 +7,5 @@ s3cmd~=2.1.0
yamllint>=1.28.0
tabulate~=0.9.0
pylint~=2.15.5
+pylint-print
python-dateutil>=2.8.2
diff --git a/examples/bzlmod/requirements_lock_3_10.txt b/examples/bzlmod/requirements_lock_3_10.txt
index e3a185ac88..7f9bd3ac4a 100644
--- a/examples/bzlmod/requirements_lock_3_10.txt
+++ b/examples/bzlmod/requirements_lock_3_10.txt
@@ -83,6 +83,12 @@ platformdirs==3.5.1 \
pylint==2.15.10 \
--hash=sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e \
--hash=sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5
+ # via
+ # -r requirements.in
+ # pylint-print
+pylint-print==1.0.1 \
+ --hash=sha256:30aa207e9718ebf4ceb47fb87012092e6d8743aab932aa07aa14a73e750ad3d0 \
+ --hash=sha256:a2b2599e7887b93e551db2624c523c1e6e9e58c3be8416cd98d41e4427e2669b
# via -r requirements.in
python-dateutil==2.8.2 \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
diff --git a/examples/bzlmod/requirements_lock_3_9.txt b/examples/bzlmod/requirements_lock_3_9.txt
index ba1d4d7148..a3bfba22ad 100644
--- a/examples/bzlmod/requirements_lock_3_9.txt
+++ b/examples/bzlmod/requirements_lock_3_9.txt
@@ -66,6 +66,12 @@ platformdirs==2.6.0 \
pylint==2.15.9 \
--hash=sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4 \
--hash=sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb
+ # via
+ # -r requirements.in
+ # pylint-print
+pylint-print==1.0.1 \
+ --hash=sha256:30aa207e9718ebf4ceb47fb87012092e6d8743aab932aa07aa14a73e750ad3d0 \
+ --hash=sha256:a2b2599e7887b93e551db2624c523c1e6e9e58c3be8416cd98d41e4427e2669b
# via -r requirements.in
python-dateutil==2.8.2 \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
@@ -127,10 +133,6 @@ 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
@@ -303,3 +305,9 @@ yamllint==1.28.0 \
--hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
--hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
# via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
diff --git a/examples/bzlmod/requirements_windows_3_10.txt b/examples/bzlmod/requirements_windows_3_10.txt
index 9a28ae8687..a8f05add6b 100644
--- a/examples/bzlmod/requirements_windows_3_10.txt
+++ b/examples/bzlmod/requirements_windows_3_10.txt
@@ -87,6 +87,12 @@ platformdirs==3.5.1 \
pylint==2.15.10 \
--hash=sha256:9df0d07e8948a1c3ffa3b6e2d7e6e63d9fb457c5da5b961ed63106594780cc7e \
--hash=sha256:b3dc5ef7d33858f297ac0d06cc73862f01e4f2e74025ec3eff347ce0bc60baf5
+ # via
+ # -r requirements.in
+ # pylint-print
+pylint-print==1.0.1 \
+ --hash=sha256:30aa207e9718ebf4ceb47fb87012092e6d8743aab932aa07aa14a73e750ad3d0 \
+ --hash=sha256:a2b2599e7887b93e551db2624c523c1e6e9e58c3be8416cd98d41e4427e2669b
# via -r requirements.in
python-dateutil==2.8.2 \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
diff --git a/examples/bzlmod/requirements_windows_3_9.txt b/examples/bzlmod/requirements_windows_3_9.txt
index 08f0979d52..2681ff2a00 100644
--- a/examples/bzlmod/requirements_windows_3_9.txt
+++ b/examples/bzlmod/requirements_windows_3_9.txt
@@ -70,6 +70,12 @@ platformdirs==2.6.0 \
pylint==2.15.9 \
--hash=sha256:18783cca3cfee5b83c6c5d10b3cdb66c6594520ffae61890858fe8d932e1c6b4 \
--hash=sha256:349c8cd36aede4d50a0754a8c0218b43323d13d5d88f4b2952ddfe3e169681eb
+ # via
+ # -r requirements.in
+ # pylint-print
+pylint-print==1.0.1 \
+ --hash=sha256:30aa207e9718ebf4ceb47fb87012092e6d8743aab932aa07aa14a73e750ad3d0 \
+ --hash=sha256:a2b2599e7887b93e551db2624c523c1e6e9e58c3be8416cd98d41e4427e2669b
# via -r requirements.in
python-dateutil==2.8.2 \
--hash=sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86 \
@@ -131,10 +137,6 @@ 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
@@ -307,3 +309,9 @@ yamllint==1.28.0 \
--hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
--hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
# via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
diff --git a/examples/bzlmod_build_file_generation/BUILD.bazel b/examples/bzlmod_build_file_generation/BUILD.bazel
index c5e27c2d49..9b2e5bdce4 100644
--- a/examples/bzlmod_build_file_generation/BUILD.bazel
+++ b/examples/bzlmod_build_file_generation/BUILD.bazel
@@ -17,7 +17,6 @@ load("@rules_python_gazelle_plugin//modules_mapping:def.bzl", "modules_mapping")
# 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",
@@ -53,6 +52,7 @@ gazelle_python_manifest(
"//:requirements_lock.txt",
"//:requirements_windows.txt",
],
+ tags = ["exclusive"],
# NOTE: we can use this flag in order to make our setup compatible with
# bzlmod.
use_pip_repository_aliases = True,
diff --git a/examples/bzlmod_build_file_generation/gazelle_python.yaml b/examples/bzlmod_build_file_generation/gazelle_python.yaml
index e33021b9c8..0c7f14876e 100644
--- a/examples/bzlmod_build_file_generation/gazelle_python.yaml
+++ b/examples/bzlmod_build_file_generation/gazelle_python.yaml
@@ -232,7 +232,6 @@ manifest:
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
@@ -588,4 +587,4 @@ manifest:
pip_repository:
name: pip
use_pip_repository_aliases: true
-integrity: cee7684391c4a8a1ff219cd354deae61cdcdee70f2076789aabd5249f3c4eca9
+integrity: 369584d55f2168d92c415f4c4ab4bc9d2d21a7fb0b0a6749437fcc771fd2f254
diff --git a/examples/bzlmod_build_file_generation/requirements_lock.txt b/examples/bzlmod_build_file_generation/requirements_lock.txt
index 2160fe1163..3fd053f777 100644
--- a/examples/bzlmod_build_file_generation/requirements_lock.txt
+++ b/examples/bzlmod_build_file_generation/requirements_lock.txt
@@ -125,10 +125,6 @@ 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
@@ -225,3 +221,9 @@ yamllint==1.28.0 \
--hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
--hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
# via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
diff --git a/examples/bzlmod_build_file_generation/requirements_windows.txt b/examples/bzlmod_build_file_generation/requirements_windows.txt
index 06cfdc332c..15e92288dc 100644
--- a/examples/bzlmod_build_file_generation/requirements_windows.txt
+++ b/examples/bzlmod_build_file_generation/requirements_windows.txt
@@ -129,10 +129,6 @@ 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
@@ -229,3 +225,9 @@ yamllint==1.28.0 \
--hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
--hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
# via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
diff --git a/examples/multi_python_versions/requirements/BUILD.bazel b/examples/multi_python_versions/requirements/BUILD.bazel
index e3184c8ac5..e3e821a68d 100644
--- a/examples/multi_python_versions/requirements/BUILD.bazel
+++ b/examples/multi_python_versions/requirements/BUILD.bazel
@@ -5,28 +5,24 @@ load("@python//3.9:defs.bzl", compile_pip_requirements_3_9 = "compile_pip_requir
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_lock_3_10.txt b/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
index 6bee4e0030..4910d13844 100644
--- a/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_10.txt
@@ -4,53 +4,75 @@
#
# 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
+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/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
index a437a397d0..35666b54b1 100644
--- a/examples/multi_python_versions/requirements/requirements_lock_3_11.txt
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_11.txt
@@ -4,53 +4,75 @@
#
# 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
+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/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
index 19303f8eff..10b5df4830 100644
--- a/examples/multi_python_versions/requirements/requirements_lock_3_8.txt
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_8.txt
@@ -4,53 +4,75 @@
#
# 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
+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/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
index 4af42ca277..0001f88d48 100644
--- a/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
+++ b/examples/multi_python_versions/requirements/requirements_lock_3_9.txt
@@ -4,53 +4,75 @@
#
# 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
+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/requirements.in
diff --git a/examples/pip_install/BUILD.bazel b/examples/pip_install/BUILD.bazel
index 35f5a9338a..87c5aa7f8c 100644
--- a/examples/pip_install/BUILD.bazel
+++ b/examples/pip_install/BUILD.bazel
@@ -61,7 +61,6 @@ alias(
# Check that our compiled requirements are up-to-date
compile_pip_requirements(
name = "requirements",
- extra_args = ["--allow-unsafe"],
requirements_windows = ":requirements_windows.txt",
)
@@ -88,7 +87,7 @@ py_test(
genquery(
name = "yamllint_lib_by_version",
expression = """
- attr("tags", "\\bpypi_version=1.26.3\\b", "@pip_yamllint//:pkg")
+ attr("tags", "\\bpypi_version=1.28.0\\b", "@pip_yamllint//:pkg")
intersect
attr("tags", "\\bpypi_name=yamllint\\b", "@pip_yamllint//:pkg")
""",
diff --git a/examples/pip_install/pip_install_test.py b/examples/pip_install/pip_install_test.py
index 3e1b085ed4..f49422bb83 100644
--- a/examples/pip_install/pip_install_test.py
+++ b/examples/pip_install/pip_install_test.py
@@ -43,7 +43,7 @@ def test_entry_point(self):
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
- self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.26.3")
+ self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.28.0")
def test_data(self):
env = os.environ.get("WHEEL_DATA_CONTENTS")
diff --git a/examples/pip_install/requirements.in b/examples/pip_install/requirements.in
index 11ede3c44a..3480175020 100644
--- a/examples/pip_install/requirements.in
+++ b/examples/pip_install/requirements.in
@@ -1,4 +1,4 @@
boto3~=1.14.51
s3cmd~=2.1.0
-yamllint~=1.26.3
+yamllint~=1.28.0
tree-sitter==0.20.0 ; sys_platform != "win32"
diff --git a/examples/pip_install/requirements.txt b/examples/pip_install/requirements.txt
index 495a32a637..00fe860169 100644
--- a/examples/pip_install/requirements.txt
+++ b/examples/pip_install/requirements.txt
@@ -89,10 +89,6 @@ 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
@@ -105,6 +101,13 @@ urllib3==1.25.11 \
--hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \
--hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e
# via botocore
-yamllint==1.26.3 \
- --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
# via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
diff --git a/examples/pip_install/requirements_windows.txt b/examples/pip_install/requirements_windows.txt
index b87192f9d0..c74eeacb06 100644
--- a/examples/pip_install/requirements_windows.txt
+++ b/examples/pip_install/requirements_windows.txt
@@ -89,18 +89,21 @@ 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
+urllib3==1.26.17 \
+ --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \
+ --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b
# via botocore
-yamllint==1.26.3 \
- --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
# via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
diff --git a/examples/pip_parse/BUILD.bazel b/examples/pip_parse/BUILD.bazel
index 653f75ce2b..b7aa5b172b 100644
--- a/examples/pip_parse/BUILD.bazel
+++ b/examples/pip_parse/BUILD.bazel
@@ -58,7 +58,6 @@ alias(
# 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",
)
diff --git a/examples/pip_parse/pip_parse_test.py b/examples/pip_parse/pip_parse_test.py
index f319cb898f..199879065c 100644
--- a/examples/pip_parse/pip_parse_test.py
+++ b/examples/pip_parse/pip_parse_test.py
@@ -41,7 +41,7 @@ def test_entry_point(self):
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
- self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.26.3")
+ self.assertEqual(proc.stdout.decode("utf-8").strip(), "yamllint 1.28.0")
def test_data(self):
env = os.environ.get("WHEEL_DATA_CONTENTS")
diff --git a/examples/pip_parse/requirements.in b/examples/pip_parse/requirements.in
index ec2102fdda..279dd6068e 100644
--- a/examples/pip_parse/requirements.in
+++ b/examples/pip_parse/requirements.in
@@ -1,3 +1,3 @@
requests~=2.25.1
s3cmd~=2.1.0
-yamllint~=1.26.3
+yamllint~=1.28.0
diff --git a/examples/pip_parse/requirements_lock.txt b/examples/pip_parse/requirements_lock.txt
index 3cbe57f28c..8b68356b29 100644
--- a/examples/pip_parse/requirements_lock.txt
+++ b/examples/pip_parse/requirements_lock.txt
@@ -78,10 +78,6 @@ 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
@@ -90,6 +86,13 @@ urllib3==1.26.13 \
--hash=sha256:47cc05d99aaa09c9e72ed5809b60e7ba354e64b59c9c173ac3018642d8bb41fc \
--hash=sha256:c083dd0dce68dbfbe1129d5271cb90f9447dea7d52097c6e0126120c521ddea8
# via requests
-yamllint==1.26.3 \
- --hash=sha256:3934dcde484374596d6b52d8db412929a169f6d9e52e20f9ade5bf3523d9b96e
+yamllint==1.28.0 \
+ --hash=sha256:89bb5b5ac33b1ade059743cf227de73daa34d5e5a474b06a5e17fc16583b0cf2 \
+ --hash=sha256:9e3d8ddd16d0583214c5fdffe806c9344086721f107435f68bad990e5a88826b
# via -r requirements.in
+
+# The following packages are considered to be unsafe in a requirements file:
+setuptools==65.6.3 \
+ --hash=sha256:57f6f22bde4e042978bcd50176fdb381d7c21a9efa4041202288d3737a0c6a54 \
+ --hash=sha256:a7620757bf984b58deaf32fc8a4577a9bbc0850cf92c20e1ce41c38c19e5fb75
+ # via yamllint
diff --git a/examples/pip_parse_vendored/BUILD.bazel b/examples/pip_parse_vendored/BUILD.bazel
index 56630e513d..b87b2aa812 100644
--- a/examples/pip_parse_vendored/BUILD.bazel
+++ b/examples/pip_parse_vendored/BUILD.bazel
@@ -16,7 +16,7 @@ genrule(
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")'""",
+ """sed -e '/^load.*.whl_library/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#@//#//#'""",
diff --git a/examples/pip_parse_vendored/requirements.bzl b/examples/pip_parse_vendored/requirements.bzl
index 7bf5170120..4e83555d6c 100644
--- a/examples/pip_parse_vendored/requirements.bzl
+++ b/examples/pip_parse_vendored/requirements.bzl
@@ -13,7 +13,7 @@ all_whl_requirements = ["@pip_certifi//:whl", "@pip_charset_normalizer//: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")]
+_packages = [("pip_certifi", "certifi==2023.7.22 --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9"), ("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 = {}
diff --git a/examples/pip_parse_vendored/requirements.in b/examples/pip_parse_vendored/requirements.in
index f2293605cf..7ec4233fa4 100644
--- a/examples/pip_parse_vendored/requirements.in
+++ b/examples/pip_parse_vendored/requirements.in
@@ -1 +1,2 @@
requests
+certifi>=2023.7.22 # https://security.snyk.io/vuln/SNYK-PYTHON-CERTIFI-5805047
diff --git a/examples/pip_parse_vendored/requirements.txt b/examples/pip_parse_vendored/requirements.txt
index ff1a3633a2..75b45a1ce3 100644
--- a/examples/pip_parse_vendored/requirements.txt
+++ b/examples/pip_parse_vendored/requirements.txt
@@ -4,10 +4,12 @@
#
# bazel run //:requirements.update
#
-certifi==2022.12.7 \
- --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
- --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
- # via requests
+certifi==2023.7.22 \
+ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \
+ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9
+ # via
+ # -r requirements.in
+ # requests
charset-normalizer==2.1.1 \
--hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \
--hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f
diff --git a/examples/pip_repository_annotations/BUILD.bazel b/examples/pip_repository_annotations/BUILD.bazel
index 84089f77d0..77b5ab0698 100644
--- a/examples/pip_repository_annotations/BUILD.bazel
+++ b/examples/pip_repository_annotations/BUILD.bazel
@@ -10,7 +10,6 @@ exports_files(
# This rule adds a convenient way to update the requirements file.
compile_pip_requirements(
name = "requirements",
- extra_args = ["--allow-unsafe"],
)
py_test(
diff --git a/examples/pip_repository_annotations/requirements.in b/examples/pip_repository_annotations/requirements.in
index fd3f75c888..29419a216c 100644
--- a/examples/pip_repository_annotations/requirements.in
+++ b/examples/pip_repository_annotations/requirements.in
@@ -2,5 +2,6 @@
# `pip_repository` rules.
--extra-index-url https://pypi.python.org/simple/
+certifi>=2023.7.22 # https://security.snyk.io/vuln/SNYK-PYTHON-CERTIFI-5805047
wheel
requests[security]>=2.8.1
diff --git a/examples/pip_repository_annotations/requirements.txt b/examples/pip_repository_annotations/requirements.txt
index 9fde0a922f..9063fa7b1c 100644
--- a/examples/pip_repository_annotations/requirements.txt
+++ b/examples/pip_repository_annotations/requirements.txt
@@ -6,10 +6,12 @@
#
--extra-index-url https://pypi.python.org/simple/
-certifi==2022.12.7 \
- --hash=sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3 \
- --hash=sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18
- # via requests
+certifi==2023.7.22 \
+ --hash=sha256:539cc1d13202e33ca466e88b2807e29f4c13049d6d87031a3c110744495cb082 \
+ --hash=sha256:92d6037539857d8206b8f6ae472e8b77db8058fec5937a1ef3f54304089edbb9
+ # via
+ # -r requirements.in
+ # requests
charset-normalizer==2.1.1 \
--hash=sha256:5a3d016c7c547f69d6f81fb0db9449ce888b418b5b9952cc5e6e66843e9dd845 \
--hash=sha256:83e9a75d1911279afd89352c68b45348559d1fc0506b054b346651b5e7fee29f
@@ -22,9 +24,9 @@ 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
+urllib3==1.26.17 \
+ --hash=sha256:24d6a242c28d29af46c3fae832c36db3bbebcc533dd1bb549172cd739c82df21 \
+ --hash=sha256:94a757d178c9be92ef5539b8840d48dc9cf1b2709c9d6b588232a055c524458b
# via requests
wheel==0.38.4 \
--hash=sha256:965f5259b566725405b05e7cf774052044b1ed30119b5d586b2703aafe8719ac \
diff --git a/examples/wheel/BUILD.bazel b/examples/wheel/BUILD.bazel
index f56a41b370..81422d37c3 100644
--- a/examples/wheel/BUILD.bazel
+++ b/examples/wheel/BUILD.bazel
@@ -54,6 +54,8 @@ py_wheel(
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",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_tag = "py3",
version = "0.0.1",
deps = [
@@ -76,6 +78,8 @@ py_wheel(
testonly = True,
abi = "$(ABI)",
distribution = "example_minimal_library",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_tag = "$(PYTHON_TAG)",
toolchains = ["//examples/wheel:make_variable_tags"],
version = "$(VERSION)",
@@ -95,6 +99,8 @@ 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}",
+ incompatible_normalize_name = False,
+ incompatible_normalize_version = False,
python_tag = "py3",
stamp = 1,
version = "0.1.{BUILD_TIMESTAMP}",
@@ -123,6 +129,8 @@ 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",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_tag = "py3",
version = "0.0.1",
deps = [":example_pkg"],
@@ -156,6 +164,8 @@ py_wheel(
"//examples/wheel:README.md": "README",
},
homepage = "www.example.com",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
license = "Apache 2.0",
project_urls = {
"Bug Tracker": "www.example.com/issues",
@@ -177,6 +187,8 @@ py_wheel(
entry_points = {
"console_scripts": ["main = foo.bar:baz"],
},
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_tag = "py3",
strip_path_prefixes = [
"examples",
@@ -191,6 +203,8 @@ 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",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_tag = "py3",
strip_path_prefixes = [
"examples/wheel/lib",
@@ -206,6 +220,8 @@ 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",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_tag = "py3",
strip_path_prefixes = [
"examples/wheel",
@@ -220,6 +236,8 @@ py_wheel(
py_wheel(
name = "python_requires_in_a_package",
distribution = "example_python_requires_in_a_package",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_requires = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*",
python_tag = "py3",
version = "0.0.1",
@@ -231,6 +249,8 @@ py_wheel(
py_wheel(
name = "use_rule_with_dir_in_outs",
distribution = "use_rule_with_dir_in_outs",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
python_tag = "py3",
version = "0.0.1",
deps = [
@@ -244,6 +264,8 @@ py_wheel(
name = "python_abi3_binary_wheel",
abi = "abi3",
distribution = "example_python_abi3_binary_wheel",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
# 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",
@@ -258,16 +280,32 @@ py_wheel(
)
py_wheel(
- name = "filename_escaping",
+ name = "legacy_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",
+ incompatible_normalize_name = False,
+ incompatible_normalize_version = False,
python_tag = "py3",
version = "0.0.1-r7",
deps = [":example_pkg"],
)
+py_wheel(
+ name = "filename_escaping",
+ # Per https://packaging.python.org/en/latest/specifications/binary-distribution-format/#escaping-and-unicode
+ # runs of "-", "_" and "." should be replaced with a single underscore.
+ # Unicode non-ascii letters aren't allowed according to
+ # https://packaging.python.org/en/latest/specifications/name-normalization/.
+ distribution = "File--Name-Escaping",
+ incompatible_normalize_name = True,
+ incompatible_normalize_version = True,
+ python_tag = "py3",
+ version = "v0.0.1.RC1+ubuntu-r7",
+ deps = [":example_pkg"],
+)
+
py_test(
name = "wheel_test",
srcs = ["wheel_test.py"],
@@ -277,6 +315,7 @@ py_test(
":custom_package_root_multi_prefix_reverse_order",
":customized",
":filename_escaping",
+ ":legacy_filename_escaping",
":minimal_with_py_library",
":minimal_with_py_library_with_stamp",
":minimal_with_py_package",
diff --git a/examples/wheel/wheel_test.py b/examples/wheel/wheel_test.py
index f51a0ecedc..671bd8ad84 100644
--- a/examples/wheel/wheel_test.py
+++ b/examples/wheel/wheel_test.py
@@ -153,13 +153,51 @@ def test_customized_wheel(self):
second = second.main:s""",
)
+ def test_legacy_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.assertEquals(
+ 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.assertEquals(
+ metadata_contents,
+ b"""\
+Metadata-Version: 2.1
+Name: file~~name-escaping
+Version: 0.0.1-r7
+
+UNKNOWN
+""",
+ )
+
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",
+ "file_name_escaping-0.0.1rc1+ubuntu.r7-py3-none-any.whl",
)
with zipfile.ZipFile(filename) as zf:
self.assertEqual(
@@ -172,20 +210,20 @@ def test_filename_escaping(self):
# 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",
+ "file_name_escaping-0.0.1rc1+ubuntu.r7.dist-info/WHEEL",
+ "file_name_escaping-0.0.1rc1+ubuntu.r7.dist-info/METADATA",
+ "file_name_escaping-0.0.1rc1+ubuntu.r7.dist-info/RECORD",
],
)
metadata_contents = zf.read(
- "file_name_escaping-0.0.1_r7.dist-info/METADATA"
+ "file_name_escaping-0.0.1rc1+ubuntu.r7.dist-info/METADATA"
)
self.assertEqual(
metadata_contents,
b"""\
Metadata-Version: 2.1
-Name: file~~name-escaping
-Version: 0.0.1-r7
+Name: File--Name-Escaping
+Version: 0.0.1rc1+ubuntu.r7
UNKNOWN
""",
diff --git a/gazelle/MODULE.bazel b/gazelle/MODULE.bazel
index ae94a5f863..8c6ad19c7b 100644
--- a/gazelle/MODULE.bazel
+++ b/gazelle/MODULE.bazel
@@ -5,8 +5,8 @@ module(
)
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")
+bazel_dep(name = "rules_go", version = "0.41.0", repo_name = "io_bazel_rules_go")
+bazel_dep(name = "gazelle", version = "0.33.0", repo_name = "bazel_gazelle")
go_deps = use_extension("@bazel_gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
diff --git a/gazelle/README.md b/gazelle/README.md
index ba8520d36b..4728e4c429 100644
--- a/gazelle/README.md
+++ b/gazelle/README.md
@@ -189,9 +189,9 @@ Python-specific directives are as follows:
| `# 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" | |
+| Controls the target generation mode. Can be "file", "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`. | |
+| 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` |
@@ -206,11 +206,15 @@ 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.
+In package generation mode, 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`.
-Next, all source files are collected into the `srcs` of the `py_library`.
+In project generation mode, all source files in subdirectories (that don't have
+BUILD files) are also collected.
+
+In file generation mode, each file is given its own target.
Finally, the `import` statements in the source files are parsed, and
dependencies are added to the `deps` attribute.
diff --git a/gazelle/WORKSPACE b/gazelle/WORKSPACE
index eef16e924d..fe7ac3ec53 100644
--- a/gazelle/WORKSPACE
+++ b/gazelle/WORKSPACE
@@ -4,10 +4,10 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "io_bazel_rules_go",
- sha256 = "099a9fb96a376ccbbb7d291ed4ecbdfd42f6bc822ab77ae6f1b5cb9e914e94fa",
+ sha256 = "278b7ff5a826f3dc10f04feaf0b70d48b68748ccd512d7f98bf442077f043fe3",
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",
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.41.0/rules_go-v0.41.0.zip",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.41.0/rules_go-v0.41.0.zip",
],
)
@@ -34,7 +34,9 @@ local_repository(
path = "..",
)
-load("@rules_python//python:repositories.bzl", "python_register_toolchains")
+load("@rules_python//python:repositories.bzl", "py_repositories", "python_register_toolchains")
+
+py_repositories()
python_register_toolchains(
name = "python39",
diff --git a/gazelle/go.mod b/gazelle/go.mod
index 1d1cee75f5..6789aa152b 100644
--- a/gazelle/go.mod
+++ b/gazelle/go.mod
@@ -5,7 +5,7 @@ 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/bazelbuild/rules_go v0.41.0
github.com/bmatcuk/doublestar v1.3.4
github.com/emirpasic/gods v1.18.1
github.com/ghodss/yaml v1.0.0
diff --git a/gazelle/go.sum b/gazelle/go.sum
index ba2c8bf688..5617f9b822 100644
--- a/gazelle/go.sum
+++ b/gazelle/go.sum
@@ -4,8 +4,8 @@ github.com/bazelbuild/bazel-gazelle v0.31.1 h1:ROyUyUHzoEdvoOs1e0haxJx1l5EjZX6AO
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/bazelbuild/rules_go v0.41.0 h1:JzlRxsFNhlX+g4drDRPhIaU5H5LnI978wdMJ0vK4I+k=
+github.com/bazelbuild/rules_go v0.41.0/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=
diff --git a/gazelle/manifest/defs.bzl b/gazelle/manifest/defs.bzl
index f1266a0f46..d5afe7c143 100644
--- a/gazelle/manifest/defs.bzl
+++ b/gazelle/manifest/defs.bzl
@@ -25,7 +25,8 @@ def gazelle_python_manifest(
pip_repository_name = "",
pip_deps_repository_name = "",
manifest = ":gazelle_python.yaml",
- use_pip_repository_aliases = False):
+ use_pip_repository_aliases = False,
+ **kwargs):
"""A macro for defining the updating and testing targets for the Gazelle manifest file.
Args:
@@ -39,6 +40,8 @@ def gazelle_python_manifest(
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.
+ **kwargs: other bazel attributes passed to the target target generated by
+ this macro.
"""
if pip_deps_repository_name != "":
# buildifier: disable=print
@@ -102,6 +105,14 @@ def gazelle_python_manifest(
tags = ["manual"],
)
+ attrs = {
+ "env": {
+ "_TEST_MANIFEST": "$(rootpath {})".format(manifest),
+ "_TEST_MANIFEST_GENERATOR_HASH": "$(rootpath {})".format(manifest_generator_hash),
+ "_TEST_REQUIREMENTS": "$(rootpath {})".format(requirements),
+ },
+ "size": "small",
+ }
go_test(
name = "{}.test".format(name),
srcs = [Label("//manifest/test:test.go")],
@@ -110,14 +121,10 @@ def gazelle_python_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",
+ # kwargs could contain test-specific attributes like size or timeout
+ **dict(attrs, **kwargs)
)
native.filegroup(
diff --git a/gazelle/python/BUILD.bazel b/gazelle/python/BUILD.bazel
index fcfe81bd61..4cb755de25 100644
--- a/gazelle/python/BUILD.bazel
+++ b/gazelle/python/BUILD.bazel
@@ -36,7 +36,7 @@ go_library(
"@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",
+ "@io_bazel_rules_go//go/runfiles",
],
)
diff --git a/gazelle/python/configure.go b/gazelle/python/configure.go
index 32f9ab0a11..2d3880571c 100644
--- a/gazelle/python/configure.go
+++ b/gazelle/python/configure.go
@@ -137,8 +137,13 @@ func (py *Configurer) Configure(c *config.Config, rel string, f *rule.File) {
switch pythonconfig.GenerationModeType(strings.TrimSpace(d.Value)) {
case pythonconfig.GenerationModePackage:
config.SetCoarseGrainedGeneration(false)
+ config.SetPerFileGeneration(false)
+ case pythonconfig.GenerationModeFile:
+ config.SetCoarseGrainedGeneration(false)
+ config.SetPerFileGeneration(true)
case pythonconfig.GenerationModeProject:
config.SetCoarseGrainedGeneration(true)
+ config.SetPerFileGeneration(false)
default:
err := fmt.Errorf("invalid value for directive %q: %s",
pythonconfig.GenerationMode, d.Value)
diff --git a/gazelle/python/generate.go b/gazelle/python/generate.go
index fb41324fd6..ede4d2a222 100644
--- a/gazelle/python/generate.go
+++ b/gazelle/python/generate.go
@@ -153,12 +153,17 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
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
+ // 1. We are using per-file generation.
+ // 2. 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.
+ // 3. (only for package generation) The directory has an
+ // __init__.py, __main__.py or __test__.py, meaning a
+ // BUILD file will be generated.
+ if cfg.PerFileGeneration() {
+ return fs.SkipDir
+ }
+
if isBazelPackage(path) {
boundaryPackages[path] = struct{}{}
return nil
@@ -213,15 +218,12 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
collisionErrors := singlylinkedlist.New()
- var pyLibrary *rule.Rule
- if !pyLibraryFilenames.Empty() {
- deps, err := parser.parse(pyLibraryFilenames)
+ appendPyLibrary := func(srcs *treeset.Set, pyLibraryTargetName string) {
+ deps, err := parser.parse(srcs)
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
@@ -239,9 +241,9 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
}
}
- pyLibrary = newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
+ pyLibrary := newTargetBuilder(pyLibraryKind, pyLibraryTargetName, pythonProjectRoot, args.Rel, pyFileNames).
addVisibility(visibility).
- addSrcs(pyLibraryFilenames).
+ addSrcs(srcs).
addModuleDependencies(deps).
generateImportsAttribute().
build()
@@ -249,6 +251,24 @@ func (py *Python) GenerateRules(args language.GenerateArgs) language.GenerateRes
result.Gen = append(result.Gen, pyLibrary)
result.Imports = append(result.Imports, pyLibrary.PrivateAttr(config.GazelleImportsKey))
}
+ if cfg.PerFileGeneration() {
+ pyLibraryFilenames.Each(func(index int, filename interface{}) {
+ if filename == pyLibraryEntrypointFilename {
+ stat, err := os.Stat(filepath.Join(args.Dir, filename.(string)))
+ if err != nil {
+ log.Fatalf("ERROR: %v\n", err)
+ }
+ if stat.Size() == 0 {
+ return // ignore empty __init__.py
+ }
+ }
+ srcs := treeset.NewWith(godsutils.StringComparator, filename)
+ pyLibraryTargetName := strings.TrimSuffix(filepath.Base(filename.(string)), ".py")
+ appendPyLibrary(srcs, pyLibraryTargetName)
+ })
+ } else if !pyLibraryFilenames.Empty() {
+ appendPyLibrary(pyLibraryFilenames, cfg.RenderLibraryName(packageName))
+ }
if hasPyBinary {
deps, err := parser.parseSingle(pyBinaryEntrypointFilename)
diff --git a/gazelle/python/kinds.go b/gazelle/python/kinds.go
index ab1afb7d55..941b45b5c6 100644
--- a/gazelle/python/kinds.go
+++ b/gazelle/python/kinds.go
@@ -49,7 +49,8 @@ var pyKinds = map[string]rule.KindInfo{
},
},
pyLibraryKind: {
- MatchAny: true,
+ MatchAny: false,
+ MatchAttrs: []string{"srcs"},
NonEmptyAttrs: map[string]bool{
"deps": true,
"srcs": true,
diff --git a/gazelle/python/parser.go b/gazelle/python/parser.go
index 7f10a754bf..60a3c24269 100644
--- a/gazelle/python/parser.go
+++ b/gazelle/python/parser.go
@@ -26,7 +26,7 @@ import (
"strings"
"sync"
- "github.com/bazelbuild/rules_go/go/tools/bazel"
+ "github.com/bazelbuild/rules_go/go/runfiles"
"github.com/emirpasic/gods/sets/treeset"
godsutils "github.com/emirpasic/gods/utils"
)
@@ -38,13 +38,20 @@ var (
)
func startParserProcess(ctx context.Context) {
- parseScriptRunfile, err := bazel.Runfile("python/parse")
+ rfiles, err := runfiles.New()
+ if err != nil {
+ log.Printf("failed to create a runfiles object: %v\n", err)
+ os.Exit(1)
+ }
+
+ parseScriptRunfile, err := rfiles.Rlocation("rules_python_gazelle_plugin/python/parse")
if err != nil {
log.Printf("failed to initialize parser: %v\n", err)
os.Exit(1)
}
cmd := exec.CommandContext(ctx, parseScriptRunfile)
+ cmd.Env = append(os.Environ(), rfiles.Env()...)
cmd.Stderr = os.Stderr
diff --git a/gazelle/python/resolve.go b/gazelle/python/resolve.go
index 46014e50ec..87eed76ec3 100644
--- a/gazelle/python/resolve.go
+++ b/gazelle/python/resolve.go
@@ -151,10 +151,10 @@ func (py *Resolver) Resolve(
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`.
+ // For example, "from foo.bar import baz" where baz is a module, 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 `baz` 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]
diff --git a/gazelle/python/std_modules.go b/gazelle/python/std_modules.go
index c537184c74..a87deec366 100644
--- a/gazelle/python/std_modules.go
+++ b/gazelle/python/std_modules.go
@@ -26,7 +26,7 @@ import (
"strings"
"sync"
- "github.com/bazelbuild/rules_go/go/tools/bazel"
+ "github.com/bazelbuild/rules_go/go/runfiles"
)
var (
@@ -39,7 +39,13 @@ var (
func startStdModuleProcess(ctx context.Context) {
stdModulesSeen = make(map[string]struct{})
- stdModulesScriptRunfile, err := bazel.Runfile("python/std_modules")
+ rfiles, err := runfiles.New()
+ if err != nil {
+ log.Printf("failed to create a runfiles object: %v\n", err)
+ os.Exit(1)
+ }
+
+ stdModulesScriptRunfile, err := rfiles.Rlocation("rules_python_gazelle_plugin/python/std_modules")
if err != nil {
log.Printf("failed to initialize std_modules: %v\n", err)
os.Exit(1)
@@ -49,7 +55,8 @@ func startStdModuleProcess(ctx context.Context) {
cmd.Stderr = os.Stderr
// All userland site-packages should be ignored.
- cmd.Env = []string{"PYTHONNOUSERSITE=1"}
+ cmd.Env = append([]string{"PYTHONNOUSERSITE=1"}, rfiles.Env()...)
+
stdin, err := cmd.StdinPipe()
if err != nil {
log.Printf("failed to initialize std_modules: %v\n", err)
diff --git a/gazelle/python/testdata/dont_rename_target/BUILD.in b/gazelle/python/testdata/dont_rename_target/BUILD.in
index 33e8ec25cb..e9bc0e6e29 100644
--- a/gazelle/python/testdata/dont_rename_target/BUILD.in
+++ b/gazelle/python/testdata/dont_rename_target/BUILD.in
@@ -2,4 +2,5 @@ load("@rules_python//python:defs.bzl", "py_library")
py_library(
name = "my_custom_target",
+ srcs = ["__init__.py"],
)
diff --git a/gazelle/python/testdata/per_file/BUILD.in b/gazelle/python/testdata/per_file/BUILD.in
new file mode 100644
index 0000000000..01b0904d50
--- /dev/null
+++ b/gazelle/python/testdata/per_file/BUILD.in
@@ -0,0 +1,11 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_generation_mode file
+
+# This target should be kept unmodified by Gazelle.
+py_library(
+ name = "custom",
+ srcs = ["bar.py"],
+ visibility = ["//visibility:private"],
+ tags = ["cant_touch_this"],
+)
diff --git a/gazelle/python/testdata/per_file/BUILD.out b/gazelle/python/testdata/per_file/BUILD.out
new file mode 100644
index 0000000000..2ec825b207
--- /dev/null
+++ b/gazelle/python/testdata/per_file/BUILD.out
@@ -0,0 +1,24 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_generation_mode file
+
+# This target should be kept unmodified by Gazelle.
+py_library(
+ name = "custom",
+ srcs = ["bar.py"],
+ tags = ["cant_touch_this"],
+ visibility = ["//visibility:private"],
+)
+
+py_library(
+ name = "baz",
+ srcs = ["baz.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_library(
+ name = "foo",
+ srcs = ["foo.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [":custom"],
+)
diff --git a/gazelle/python/testdata/per_file/README.md b/gazelle/python/testdata/per_file/README.md
new file mode 100644
index 0000000000..3ddeb213fc
--- /dev/null
+++ b/gazelle/python/testdata/per_file/README.md
@@ -0,0 +1,5 @@
+# Per-file generation
+
+This test case generates one `py_library` per file.
+
+`__init__.py` is left empty so no target is generated for it.
diff --git a/gazelle/python/testdata/per_file/WORKSPACE b/gazelle/python/testdata/per_file/WORKSPACE
new file mode 100644
index 0000000000..faff6af87a
--- /dev/null
+++ b/gazelle/python/testdata/per_file/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/per_file/__init__.py b/gazelle/python/testdata/per_file/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gazelle/python/testdata/per_file/bar.py b/gazelle/python/testdata/per_file/bar.py
new file mode 100644
index 0000000000..730755995d
--- /dev/null
+++ b/gazelle/python/testdata/per_file/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/per_file/baz.py b/gazelle/python/testdata/per_file/baz.py
new file mode 100644
index 0000000000..730755995d
--- /dev/null
+++ b/gazelle/python/testdata/per_file/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/per_file/foo.py b/gazelle/python/testdata/per_file/foo.py
new file mode 100644
index 0000000000..c000990002
--- /dev/null
+++ b/gazelle/python/testdata/per_file/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.
+
+import bar
diff --git a/gazelle/python/testdata/per_file/test.yaml b/gazelle/python/testdata/per_file/test.yaml
new file mode 100644
index 0000000000..fcea77710f
--- /dev/null
+++ b/gazelle/python/testdata/per_file/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/per_file_non_empty_init/BUILD.in b/gazelle/python/testdata/per_file_non_empty_init/BUILD.in
new file mode 100644
index 0000000000..a5853f6c5c
--- /dev/null
+++ b/gazelle/python/testdata/per_file_non_empty_init/BUILD.in
@@ -0,0 +1,3 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_generation_mode file
diff --git a/gazelle/python/testdata/per_file_non_empty_init/BUILD.out b/gazelle/python/testdata/per_file_non_empty_init/BUILD.out
new file mode 100644
index 0000000000..8733dbd971
--- /dev/null
+++ b/gazelle/python/testdata/per_file_non_empty_init/BUILD.out
@@ -0,0 +1,16 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_generation_mode file
+
+py_library(
+ name = "__init__",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+ deps = [":foo"],
+)
+
+py_library(
+ name = "foo",
+ srcs = ["foo.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/per_file_non_empty_init/README.md b/gazelle/python/testdata/per_file_non_empty_init/README.md
new file mode 100644
index 0000000000..6e6e9e245d
--- /dev/null
+++ b/gazelle/python/testdata/per_file_non_empty_init/README.md
@@ -0,0 +1,3 @@
+# Per-file generation
+
+This test case generates one `py_library` per file, including `__init__.py`.
diff --git a/gazelle/python/testdata/per_file_non_empty_init/WORKSPACE b/gazelle/python/testdata/per_file_non_empty_init/WORKSPACE
new file mode 100644
index 0000000000..faff6af87a
--- /dev/null
+++ b/gazelle/python/testdata/per_file_non_empty_init/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/per_file_non_empty_init/__init__.py b/gazelle/python/testdata/per_file_non_empty_init/__init__.py
new file mode 100644
index 0000000000..492cbc0260
--- /dev/null
+++ b/gazelle/python/testdata/per_file_non_empty_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.
+
+import foo
diff --git a/gazelle/python/testdata/per_file_non_empty_init/foo.py b/gazelle/python/testdata/per_file_non_empty_init/foo.py
new file mode 100644
index 0000000000..730755995d
--- /dev/null
+++ b/gazelle/python/testdata/per_file_non_empty_init/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/per_file_non_empty_init/test.yaml b/gazelle/python/testdata/per_file_non_empty_init/test.yaml
new file mode 100644
index 0000000000..fcea77710f
--- /dev/null
+++ b/gazelle/python/testdata/per_file_non_empty_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/per_file_subdirs/BUILD.in b/gazelle/python/testdata/per_file_subdirs/BUILD.in
new file mode 100644
index 0000000000..a5853f6c5c
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/BUILD.in
@@ -0,0 +1,3 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_generation_mode file
diff --git a/gazelle/python/testdata/per_file_subdirs/BUILD.out b/gazelle/python/testdata/per_file_subdirs/BUILD.out
new file mode 100644
index 0000000000..69c42e01a9
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/BUILD.out
@@ -0,0 +1,10 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+# gazelle:python_generation_mode file
+
+py_library(
+ name = "foo",
+ srcs = ["foo.py"],
+ visibility = ["//:__subpackages__"],
+ deps = ["//bar:__init__"],
+)
diff --git a/gazelle/python/testdata/per_file_subdirs/README.md b/gazelle/python/testdata/per_file_subdirs/README.md
new file mode 100644
index 0000000000..9eda2fac28
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/README.md
@@ -0,0 +1,3 @@
+# Per-file generation
+
+This test case generates one `py_library` per file in subdirectories.
diff --git a/gazelle/python/testdata/per_file_subdirs/WORKSPACE b/gazelle/python/testdata/per_file_subdirs/WORKSPACE
new file mode 100644
index 0000000000..faff6af87a
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/WORKSPACE
@@ -0,0 +1 @@
+# This is a Bazel workspace for the Gazelle test data.
diff --git a/gazelle/python/testdata/per_file_subdirs/bar/BUILD.in b/gazelle/python/testdata/per_file_subdirs/bar/BUILD.in
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/gazelle/python/testdata/per_file_subdirs/bar/BUILD.out b/gazelle/python/testdata/per_file_subdirs/bar/BUILD.out
new file mode 100644
index 0000000000..7258d27524
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/bar/BUILD.out
@@ -0,0 +1,13 @@
+load("@rules_python//python:defs.bzl", "py_library")
+
+py_library(
+ name = "__init__",
+ srcs = ["__init__.py"],
+ visibility = ["//:__subpackages__"],
+)
+
+py_library(
+ name = "foo",
+ srcs = ["foo.py"],
+ visibility = ["//:__subpackages__"],
+)
diff --git a/gazelle/python/testdata/per_file_subdirs/bar/__init__.py b/gazelle/python/testdata/per_file_subdirs/bar/__init__.py
new file mode 100644
index 0000000000..579915261d
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/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.
+
+from .foo import func
diff --git a/gazelle/python/testdata/per_file_subdirs/bar/foo.py b/gazelle/python/testdata/per_file_subdirs/bar/foo.py
new file mode 100644
index 0000000000..59eb08c42f
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/bar/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 func():
+ pass
diff --git a/gazelle/python/testdata/per_file_subdirs/baz/baz.py b/gazelle/python/testdata/per_file_subdirs/baz/baz.py
new file mode 100644
index 0000000000..5256394021
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/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.
+
+from bar.foo import func
diff --git a/gazelle/python/testdata/per_file_subdirs/foo.py b/gazelle/python/testdata/per_file_subdirs/foo.py
new file mode 100644
index 0000000000..b5e6cff5c6
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/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.
+
+from bar import func
diff --git a/gazelle/python/testdata/per_file_subdirs/test.yaml b/gazelle/python/testdata/per_file_subdirs/test.yaml
new file mode 100644
index 0000000000..fcea77710f
--- /dev/null
+++ b/gazelle/python/testdata/per_file_subdirs/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/pythonconfig.go b/gazelle/pythonconfig/pythonconfig.go
index c7cd7c1a28..a266804fab 100644
--- a/gazelle/pythonconfig/pythonconfig.go
+++ b/gazelle/pythonconfig/pythonconfig.go
@@ -78,6 +78,7 @@ const (
// GenerationModeProject defines the mode in which a coarse-grained target will
// be generated englobing sub-directories containing Python files.
GenerationModeProject GenerationModeType = "project"
+ GenerationModeFile GenerationModeType = "file"
)
const (
@@ -126,6 +127,7 @@ type Config struct {
ignoreDependencies map[string]struct{}
validateImportStatements bool
coarseGrainedGeneration bool
+ perFileGeneration bool
libraryNamingConvention string
binaryNamingConvention string
testNamingConvention string
@@ -145,6 +147,7 @@ func New(
ignoreDependencies: make(map[string]struct{}),
validateImportStatements: true,
coarseGrainedGeneration: false,
+ perFileGeneration: false,
libraryNamingConvention: packageNameNamingConventionSubstitution,
binaryNamingConvention: fmt.Sprintf("%s_bin", packageNameNamingConventionSubstitution),
testNamingConvention: fmt.Sprintf("%s_test", packageNameNamingConventionSubstitution),
@@ -169,6 +172,7 @@ func (c *Config) NewChild() *Config {
ignoreDependencies: make(map[string]struct{}),
validateImportStatements: c.validateImportStatements,
coarseGrainedGeneration: c.coarseGrainedGeneration,
+ perFileGeneration: c.perFileGeneration,
libraryNamingConvention: c.libraryNamingConvention,
binaryNamingConvention: c.binaryNamingConvention,
testNamingConvention: c.testNamingConvention,
@@ -327,6 +331,18 @@ func (c *Config) CoarseGrainedGeneration() bool {
return c.coarseGrainedGeneration
}
+// SetPerFileGneration sets whether a separate py_library target should be
+// generated for each file.
+func (c *Config) SetPerFileGeneration(perFile bool) {
+ c.perFileGeneration = perFile
+}
+
+// PerFileGeneration returns whether a separate py_library target should be
+// generated for each file.
+func (c *Config) PerFileGeneration() bool {
+ return c.perFileGeneration
+}
+
// SetLibraryNamingConvention sets the py_library target naming convention.
func (c *Config) SetLibraryNamingConvention(libraryNamingConvention string) {
c.libraryNamingConvention = libraryNamingConvention
diff --git a/internal_deps.bzl b/internal_deps.bzl
index f50d2bfae1..fd2d91edc1 100644
--- a/internal_deps.bzl
+++ b/internal_deps.bzl
@@ -74,10 +74,10 @@ def rules_python_internal_deps():
maybe(
http_archive,
name = "io_bazel_rules_go",
- sha256 = "6dc2da7ab4cf5d7bfc7c949776b1b7c733f05e56edc4bcd9022bb249d2e2a996",
+ sha256 = "278b7ff5a826f3dc10f04feaf0b70d48b68748ccd512d7f98bf442077f043fe3",
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",
+ "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.41.0/rules_go-v0.41.0.zip",
+ "https://github.com/bazelbuild/rules_go/releases/download/v0.41.0/rules_go-v0.41.0.zip",
],
)
diff --git a/internal_setup.bzl b/internal_setup.bzl
index c3a7ad452d..0c9d6c48a6 100644
--- a/internal_setup.bzl
+++ b/internal_setup.bzl
@@ -20,10 +20,13 @@ 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")
+load("//python/private:internal_config_repo.bzl", "internal_config_repo") # buildifier: disable=bzl-visibility
def rules_python_internal_setup():
"""Setup for rules_python tests and tools."""
+ internal_config_repo(name = "rules_python_internal")
+
# Because we don't use the pip_install rule, we have to call this to fetch its deps
pip_install_dependencies()
diff --git a/python/BUILD.bazel b/python/BUILD.bazel
index c5f25803c7..5ff752e13f 100644
--- a/python/BUILD.bazel
+++ b/python/BUILD.bazel
@@ -36,6 +36,7 @@ filegroup(
"//python/cc:distribution",
"//python/config_settings:distribution",
"//python/constraints:distribution",
+ "//python/entry_points:distribution",
"//python/private:distribution",
"//python/runfiles:distribution",
],
@@ -69,6 +70,32 @@ bzl_library(
],
)
+bzl_library(
+ name = "packaging_bzl",
+ srcs = ["packaging.bzl"],
+ deps = [
+ ":py_binary_bzl",
+ "//python/private:py_package.bzl",
+ "//python/private:py_wheel_bzl",
+ "//python/private:py_wheel_normalize_pep440.bzl",
+ "//python/private:stamp_bzl",
+ "//python/private:util_bzl",
+ ],
+)
+
+bzl_library(
+ name = "pip_bzl",
+ srcs = ["pip.bzl"],
+ deps = [
+ "//python/pip_install:pip_repository_bzl",
+ "//python/pip_install:repositories_bzl",
+ "//python/pip_install:requirements_bzl",
+ "//python/private:bzlmod_enabled_bzl",
+ "//python/private:full_version_bzl",
+ "//python/private:render_pkg_aliases_bzl",
+ ],
+)
+
bzl_library(
name = "proto_bzl",
srcs = [
@@ -83,7 +110,11 @@ bzl_library(
bzl_library(
name = "py_binary_bzl",
srcs = ["py_binary.bzl"],
- deps = ["//python/private:util_bzl"],
+ deps = [
+ "//python/private:util_bzl",
+ "//python/private/common:py_binary_macro_bazel_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
)
bzl_library(
@@ -100,37 +131,82 @@ bzl_library(
bzl_library(
name = "py_info_bzl",
srcs = ["py_info.bzl"],
- deps = ["//python/private:reexports_bzl"],
+ deps = [
+ "//python/private:reexports_bzl",
+ "//python/private/common:providers_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
)
bzl_library(
name = "py_library_bzl",
srcs = ["py_library.bzl"],
- deps = ["//python/private:util_bzl"],
+ deps = [
+ "//python/private:util_bzl",
+ "//python/private/common:py_library_macro_bazel_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
)
bzl_library(
name = "py_runtime_bzl",
srcs = ["py_runtime.bzl"],
- deps = ["//python/private:util_bzl"],
+ deps = [
+ "//python/private:util_bzl",
+ "//python/private/common:py_runtime_macro_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
)
bzl_library(
name = "py_runtime_pair_bzl",
srcs = ["py_runtime_pair.bzl"],
- deps = ["//python/private:bazel_tools_bzl"],
+ deps = [
+ "//python/private:bazel_tools_bzl",
+ "//python/private:py_runtime_pair_macro_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
)
bzl_library(
name = "py_runtime_info_bzl",
srcs = ["py_runtime_info.bzl"],
- deps = ["//python/private:reexports_bzl"],
+ deps = [
+ "//python/private:reexports_bzl",
+ "//python/private/common:providers_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
)
bzl_library(
name = "py_test_bzl",
srcs = ["py_test.bzl"],
- deps = ["//python/private:util_bzl"],
+ deps = [
+ "//python/private:util_bzl",
+ "//python/private/common:py_test_macro_bazel_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
+)
+
+bzl_library(
+ name = "repositories_bzl",
+ srcs = ["repositories.bzl"],
+ deps = [
+ ":versions_bzl",
+ "//python/private:bazel_tools_bzl",
+ "//python/private:bzlmod_enabled_bzl",
+ "//python/private:coverage_deps_bzl",
+ "//python/private:full_version_bzl",
+ "//python/private:internal_config_repo_bzl",
+ "//python/private:toolchains_repo_bzl",
+ "//python/private:which_bzl",
+ ],
+)
+
+bzl_library(
+ name = "versions_bzl",
+ srcs = ["versions.bzl"],
+ visibility = ["//:__subpackages__"],
)
# NOTE: Remember to add bzl_library targets to //tests:bzl_libraries
diff --git a/python/config_settings/BUILD.bazel b/python/config_settings/BUILD.bazel
index 272ba78f1f..ab4ee8d880 100644
--- a/python/config_settings/BUILD.bazel
+++ b/python/config_settings/BUILD.bazel
@@ -5,6 +5,7 @@ filegroup(
name = "distribution",
srcs = glob(["*.bzl"]) + [
"BUILD.bazel",
+ "//python/config_settings/private:distribution",
],
visibility = ["//python:__pkg__"],
)
diff --git a/python/config_settings/private/BUILD.bazel b/python/config_settings/private/BUILD.bazel
index e69de29bb2..aa68c6508c 100644
--- a/python/config_settings/private/BUILD.bazel
+++ b/python/config_settings/private/BUILD.bazel
@@ -0,0 +1,7 @@
+filegroup(
+ name = "distribution",
+ srcs = glob(["*.bzl"]) + [
+ "BUILD.bazel",
+ ],
+ visibility = ["//python/config_settings:__pkg__"],
+)
diff --git a/python/config_settings/transition.bzl b/python/config_settings/transition.bzl
index 20e03dc21d..cb25965f76 100644
--- a/python/config_settings/transition.bzl
+++ b/python/config_settings/transition.bzl
@@ -17,7 +17,8 @@ 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:py_binary.bzl", _py_binary = "py_binary")
+load("//python:py_test.bzl", _py_test = "py_test")
load("//python/config_settings/private:py_args.bzl", "py_args")
def _transition_python_version_impl(_, attr):
@@ -151,7 +152,6 @@ def _py_rule(rule_impl, transition_rule, name, python_version, **kwargs):
# 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)
@@ -165,7 +165,6 @@ def _py_rule(rule_impl, transition_rule, name, python_version, **kwargs):
common_attrs = {
"compatible_with": compatible_with,
"deprecation": deprecation,
- "distribs": distribs,
"exec_compatible_with": exec_compatible_with,
"exec_properties": exec_properties,
"features": features,
diff --git a/python/entry_points/BUILD.bazel b/python/entry_points/BUILD.bazel
new file mode 100644
index 0000000000..981a1ccf69
--- /dev/null
+++ b/python/entry_points/BUILD.bazel
@@ -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.
+
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+exports_files(
+ [
+ "py_console_script_binary.bzl",
+ ],
+ visibility = ["//docs:__subpackages__"],
+)
+
+bzl_library(
+ name = "py_console_script_binary_bzl",
+ srcs = [":py_console_script_binary.bzl"],
+ visibility = ["//visibility:public"],
+ deps = [
+ "//python/private:py_console_script_binary_bzl",
+ ],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob([
+ "*.bzl",
+ ]),
+ visibility = ["//python:__subpackages__"],
+)
diff --git a/python/entry_points/py_console_script_binary.bzl b/python/entry_points/py_console_script_binary.bzl
new file mode 100644
index 0000000000..60e74f579d
--- /dev/null
+++ b/python/entry_points/py_console_script_binary.bzl
@@ -0,0 +1,79 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+Creates an executable (a non-test binary) for console_script entry points.
+
+Generate a `py_binary` target for a particular console_script `entry_point`
+from a PyPI package, e.g. for creating an executable `pylint` target use:
+```starlark
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "pylint",
+ pkg = "@pip//pylint",
+)
+```
+
+Or for more advanced setups you can also specify extra dependencies and the
+exact script name you want to call. It is useful for tools like flake8, pylint,
+pytest, which have plugin discovery methods and discover dependencies from the
+PyPI packages available in the PYTHONPATH.
+```starlark
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "pylint_with_deps",
+ pkg = "@pip//pylint",
+ # Because `pylint` has multiple console_scripts available, we have to
+ # specify which we want if the name of the target name 'pylint_with_deps'
+ # cannot be used to guess the entry_point script.
+ script = "pylint",
+ deps = [
+ # One can add extra dependencies to the entry point.
+ # This specifically allows us to add plugins to pylint.
+ "@pip//pylint_print",
+ ],
+)
+```
+
+A specific Python version can be forced by using the generated version-aware
+wrappers, e.g. to force Python 3.9:
+```starlark
+load("@python_versions//3.9:defs.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "yamllint",
+ pkg = "@pip//yamllint",
+)
+```
+
+Alternatively, the the `py_console_script_binary.binary_rule` arg can be passed
+the version-bound `py_binary` symbol, or any other `py_binary`-compatible rule
+of your choosing:
+```starlark
+load("@python_versions//3.9:defs.bzl", "py_binary")
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "yamllint",
+ pkg = "@pip//yamllint:pkg",
+ binary_rule = py_binary,
+)
+```
+"""
+
+load("//python/private:py_console_script_binary.bzl", _py_console_script_binary = "py_console_script_binary")
+
+py_console_script_binary = _py_console_script_binary
diff --git a/python/extensions/pip.bzl b/python/extensions/pip.bzl
index 3ba0d3eb58..a0559ffe97 100644
--- a/python/extensions/pip.bzl
+++ b/python/extensions/pip.bzl
@@ -14,18 +14,18 @@
"pip module extension for use with bzlmod"
+load("@bazel_features//:features.bzl", "bazel_features")
load("@pythons_hub//:interpreters.bzl", "DEFAULT_PYTHON_VERSION", "INTERPRETER_LABELS")
-load("//python:pip.bzl", "whl_library_alias")
load(
"//python/pip_install:pip_repository.bzl",
"locked_requirements_label",
"pip_hub_repository_bzlmod",
"pip_repository_attrs",
- "pip_repository_bzlmod",
"use_isolated",
"whl_library",
)
load("//python/pip_install:requirements_parser.bzl", parse_requirements = "parse")
+load("//python/private:full_version.bzl", "full_version")
load("//python/private:normalize_name.bzl", "normalize_name")
load("//python/private:version_label.bzl", "version_label")
@@ -78,11 +78,11 @@ You cannot use both the additive_build_content and additive_build_content_file a
whl_mods = whl_mods,
)
-def _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, whl_map):
+def _create_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.
+ # we programmatically find it.
hub_name = pip_attr.hub_name
if python_interpreter_target == None:
python_name = "python_" + version_label(pip_attr.python_version, sep = "_")
@@ -104,23 +104,12 @@ def _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, whl_map):
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.
+ # needed for the whl_libary declarations below.
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] = {}
@@ -157,12 +146,12 @@ def _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, whl_map):
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 + "_"
+ whl_map[hub_name][whl_name][full_version(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.
+ """Implementation of a class tag that creates the pip hub and corresponding pip spoke whl repositories.
- This implmentation iterates through all of the `pip.parse` calls and creates
+ This implementation 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.
@@ -196,52 +185,33 @@ def _pip_impl(module_ctx):
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
+ each pip spoke had a requirements 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
+ And if the wheel has any other dependencies subsequent wheels are created in the same fashion.
- 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.
+ The hub repository has aliases for `pkg`, `data`, etc, which have 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
+ 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
+ we cannot have the same hub name used in sub-modules. 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
+ 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.
@@ -259,63 +229,46 @@ def _pip_impl(module_ctx):
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:
+ if hub_name not in pip_hub_map:
pip_hub_map[pip_attr.hub_name] = struct(
module_name = mod.name,
python_versions = [pip_attr.python_version],
)
+ elif pip_hub_map[hub_name].module_name != mod.name:
+ # We cannot have two hubs with the same name in different
+ # modules.
+ 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,
+ ))
+
+ elif 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)
- _create_versioned_pip_and_whl_repos(module_ctx, pip_attr, hub_whl_map)
+ _create_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 in version_map:
- whl_default_version = DEFAULT_PYTHON_VERSION
- else:
- whl_default_version = None
-
- # 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 = whl_default_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(),
+ whl_map = whl_map,
+ default_version = full_version(DEFAULT_PYTHON_VERSION),
)
def _pip_parse_ext_attrs():
@@ -428,6 +381,17 @@ cannot have a child module that uses the same `hub_name`.
}
return attrs
+def _extension_extra_args():
+ args = {}
+
+ if bazel_features.external_deps.module_extension_has_os_arch_dependent:
+ args = args | {
+ "arch_dependent": True,
+ "os_dependent": True,
+ }
+
+ return args
+
pip = module_extension(
doc = """\
This extension is used to make dependencies from pip available.
@@ -470,6 +434,7 @@ extension.
""",
),
},
+ **_extension_extra_args()
)
def _whl_mods_repo_impl(rctx):
diff --git a/python/extensions/private/internal_deps.bzl b/python/extensions/private/internal_deps.bzl
index 8a98b82827..aadf2cc997 100644
--- a/python/extensions/private/internal_deps.bzl
+++ b/python/extensions/private/internal_deps.bzl
@@ -9,9 +9,11 @@
"Python toolchain module extension for internal rule use"
load("//python/pip_install:repositories.bzl", "pip_install_dependencies")
+load("//python/private:internal_config_repo.bzl", "internal_config_repo")
# buildifier: disable=unused-variable
def _internal_deps_impl(module_ctx):
+ internal_config_repo(name = "rules_python_internal")
pip_install_dependencies()
internal_deps = module_extension(
diff --git a/python/extensions/private/pythons_hub.bzl b/python/extensions/private/pythons_hub.bzl
index a64f203bd6..f36ce45521 100644
--- a/python/extensions/private/pythons_hub.bzl
+++ b/python/extensions/private/pythons_hub.bzl
@@ -14,7 +14,8 @@
"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:versions.bzl", "WINDOWS_NAME")
+load("//python/private:full_version.bzl", "full_version")
load(
"//python/private:toolchains_repo.bzl",
"get_host_os_arch",
@@ -28,12 +29,6 @@ def _have_same_length(*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,
@@ -55,7 +50,7 @@ def _python_toolchain_build_file_content(
# 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]),
+ python_version = full_version(python_versions[i]),
set_python_version_constraint = set_python_version_constraints[i],
user_repository_name = user_repository_names[i],
rules_python = rules_python,
diff --git a/python/extensions/python.bzl b/python/extensions/python.bzl
index 2d007267b1..c7c2c82c05 100644
--- a/python/extensions/python.bzl
+++ b/python/extensions/python.bzl
@@ -86,16 +86,22 @@ def _python_impl(module_ctx):
# 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,
- )
+ # If the python version is explicitly provided by the root
+ # module, they should not be warned for choosing the same
+ # version that rules_python provides as default.
+ first = global_toolchain_versions[toolchain_version]
+ if mod.name != "rules_python" or not first.is_root:
+ _warn_duplicate_global_toolchain_version(
+ toolchain_version,
+ first = first,
+ 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,
+ is_root = mod.is_root,
)
# Only the root module and rules_python are allowed to specify the default
diff --git a/python/packaging.bzl b/python/packaging.bzl
index d9b9d02711..48423e307f 100644
--- a/python/packaging.bzl
+++ b/python/packaging.bzl
@@ -14,6 +14,7 @@
"""Public API for for building wheels."""
+load("//python:py_binary.bzl", "py_binary")
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")
@@ -167,7 +168,7 @@ def py_wheel(name, twine = None, publish_args = [], **kwargs):
# TODO: use py_binary from //python:defs.bzl after our stardoc setup is less brittle
# buildifier: disable=native-py
- native.py_binary(
+ py_binary(
name = "{}.publish".format(name),
srcs = [twine_main],
args = twine_args,
diff --git a/python/pip.bzl b/python/pip.bzl
index 0c6e90f577..fb842cc4ce 100644
--- a/python/pip.bzl
+++ b/python/pip.bzl
@@ -17,8 +17,8 @@ load("//python/pip_install:pip_repository.bzl", "pip_repository", _package_annot
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("//python/private:full_version.bzl", "full_version")
load("//python/private:render_pkg_aliases.bzl", "NO_MATCH_ERROR_MESSAGE_TEMPLATE")
-load(":versions.bzl", "MINOR_MAPPING")
compile_pip_requirements = _compile_pip_requirements
package_annotation = _package_annotation
@@ -295,7 +295,7 @@ alias(
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,
+ full_python_version = full_version(python_version),
actual = "@{repo_prefix}{wheel_name}//:{alias_name}".format(
repo_prefix = repo_prefix,
wheel_name = wheel_name,
diff --git a/python/pip_install/BUILD.bazel b/python/pip_install/BUILD.bazel
index 4e4fbb4a1c..c071033384 100644
--- a/python/pip_install/BUILD.bazel
+++ b/python/pip_install/BUILD.bazel
@@ -1,3 +1,70 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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")
+
+package(
+ default_visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
+ name = "pip_repository_bzl",
+ srcs = ["pip_repository.bzl"],
+ # Semi-public: What is intended to be public and what is intended to be
+ # internal is unclear. Some symbols are clearly public (e.g.
+ # package_annotations), some are clearly internal (e.g.
+ # pip_hub_repository_bzlmod), and many are unknown.
+ visibility = ["//visibility:public"],
+ deps = [
+ ":repositories_bzl",
+ ":requirements_parser_bzl",
+ "//python:repositories_bzl",
+ "//python:versions_bzl",
+ "//python/pip_install/private:generate_whl_library_build_bazel_bzl",
+ "//python/pip_install/private:srcs_bzl",
+ "//python/private:bzlmod_enabled_bzl",
+ "//python/private:normalize_name_bzl",
+ "//python/private:render_pkg_aliases_bzl",
+ "//python/private:toolchains_repo_bzl",
+ "//python/private:which_bzl",
+ ],
+)
+
+bzl_library(
+ name = "requirements_bzl",
+ srcs = ["requirements.bzl"],
+ deps = [
+ ":repositories_bzl",
+ "//python:defs_bzl",
+ ],
+)
+
+bzl_library(
+ name = "requirements_parser_bzl",
+ srcs = ["requirements_parser.bzl"],
+)
+
+bzl_library(
+ name = "repositories_bzl",
+ srcs = ["repositories.bzl"],
+ deps = [
+ "//:version_bzl",
+ "//python/private:bazel_tools_bzl",
+ "@bazel_skylib//lib:versions",
+ ],
+)
+
filegroup(
name = "distribution",
srcs = glob(["*.bzl"]) + [
diff --git a/python/pip_install/pip_hub_repository_requirements_bzlmod.bzl.tmpl b/python/pip_install/pip_hub_repository_requirements_bzlmod.bzl.tmpl
deleted file mode 100644
index 4a3d512ae7..0000000000
--- a/python/pip_install/pip_hub_repository_requirements_bzlmod.bzl.tmpl
+++ /dev/null
@@ -1,35 +0,0 @@
-"""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
index 87c7f6b77a..ea8b9eb5ac 100644
--- a/python/pip_install/pip_repository.bzl
+++ b/python/pip_install/pip_repository.bzl
@@ -14,7 +14,7 @@
""
-load("//python:repositories.bzl", "get_interpreter_dirname", "is_standalone_interpreter")
+load("//python:repositories.bzl", "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")
@@ -43,15 +43,11 @@ def _construct_pypath(rctx):
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])
+ pypath = separator.join([
+ str(rctx.path(entry).dirname)
+ for entry in rctx.attr._python_path_entries
+ ])
return pypath
def _get_python_interpreter_attr(rctx):
@@ -123,7 +119,7 @@ def _get_xcode_location_cflags(rctx):
"-isysroot {}/SDKs/MacOSX.sdk".format(xcode_root),
]
-def _get_toolchain_unix_cflags(rctx):
+def _get_toolchain_unix_cflags(rctx, python_interpreter):
"""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
@@ -135,11 +131,11 @@ def _get_toolchain_unix_cflags(rctx):
return []
# Only update the location when using a standalone toolchain.
- if not is_standalone_interpreter(rctx, rctx.attr.python_interpreter_target):
+ if not is_standalone_interpreter(rctx, python_interpreter):
return []
er = rctx.execute([
- rctx.path(rctx.attr.python_interpreter_target).realpath,
+ python_interpreter,
"-c",
"import sys; print(f'{sys.version_info[0]}.{sys.version_info[1]}', end='')",
])
@@ -147,7 +143,7 @@ def _get_toolchain_unix_cflags(rctx):
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_interpreter.dirname,
_python_version,
)
@@ -218,11 +214,12 @@ def _parse_optional_attrs(rctx, args):
return args
-def _create_repository_execution_environment(rctx):
+def _create_repository_execution_environment(rctx, python_interpreter):
"""Create a environment dictionary for processes we spawn with rctx.execute.
Args:
- rctx: The repository context.
+ rctx (repository_ctx): The repository context.
+ python_interpreter (path): The resolved python interpreter.
Returns:
Dictionary of environment variable suitable to pass to rctx.execute.
"""
@@ -230,7 +227,7 @@ def _create_repository_execution_environment(rctx):
# Gather any available CPPFLAGS values
cppflags = []
cppflags.extend(_get_xcode_location_cflags(rctx))
- cppflags.extend(_get_toolchain_unix_cflags(rctx))
+ cppflags.extend(_get_toolchain_unix_cflags(rctx, python_interpreter))
env = {
"PYTHONPATH": _construct_pypath(rctx),
@@ -270,10 +267,14 @@ A requirements_lock attribute must be specified, or a platform-specific lockfile
""")
return requirements_txt
-def _create_pip_repository_bzlmod(rctx, bzl_packages, requirements):
- repo_name = rctx.attr.repo_name
- build_contents = _BUILD_FILE_CONTENTS
- aliases = render_pkg_aliases(repo_name = repo_name, bzl_packages = bzl_packages)
+def _pip_hub_repository_bzlmod_impl(rctx):
+ bzl_packages = rctx.attr.whl_map.keys()
+ aliases = render_pkg_aliases(
+ repo_name = rctx.attr.repo_name,
+ rules_python = rctx.attr._template.workspace_name,
+ default_version = rctx.attr.default_version,
+ whl_map = rctx.attr.whl_map,
+ )
for path, contents in aliases.items():
rctx.file(path, contents)
@@ -283,7 +284,7 @@ def _create_pip_repository_bzlmod(rctx, bzl_packages, requirements):
# `requirement`, et al. macros.
macro_tmpl = "@@{name}//{{}}:{{}}".format(name = rctx.attr.name)
- rctx.file("BUILD.bazel", build_contents)
+ rctx.file("BUILD.bazel", _BUILD_FILE_CONTENTS)
rctx.template("requirements.bzl", rctx.attr._template, substitutions = {
"%%ALL_DATA_REQUIREMENTS%%": _format_repr_list([
macro_tmpl.format(p, "data")
@@ -299,24 +300,26 @@ def _create_pip_repository_bzlmod(rctx, bzl_packages, requirements):
]),
"%%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 = {
+ "default_version": attr.string(
+ mandatory = True,
+ doc = """\
+This is the default python version in the format of X.Y.Z. This should match
+what is setup by the 'python' extension using the 'is_default = True'
+setting.""",
+ ),
"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(
+ "whl_map": attr.string_list_dict(
mandatory = True,
- doc = "The list of whl alias that we use to build aliases and the whl names",
+ doc = "The wheel map where values are python versions",
),
"_template": attr.label(
- default = ":pip_hub_repository_requirements_bzlmod.bzl.tmpl",
+ default = ":pip_repository_requirements_bzlmod.bzl.tmpl",
),
}
@@ -326,52 +329,6 @@ pip_hub_repository_bzlmod = repository_rule(
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)
@@ -630,7 +587,7 @@ def _whl_library_impl(rctx):
result = rctx.execute(
args,
# Manually construct the PYTHONPATH since we cannot use the toolchain here
- environment = _create_repository_execution_environment(rctx),
+ environment = _create_repository_execution_environment(rctx, python_interpreter),
quiet = rctx.attr.quiet,
timeout = rctx.attr.timeout,
)
@@ -720,6 +677,19 @@ whl_library_attrs = {
mandatory = True,
doc = "Python requirement string describing the package to make available",
),
+ "_python_path_entries": attr.label_list(
+ # Get the root directory of these rules and keep them as a default attribute
+ # in order to avoid unnecessary repository fetching restarts.
+ #
+ # This is very similar to what was done in https://github.com/bazelbuild/rules_go/pull/3478
+ default = [
+ Label("//:BUILD.bazel"),
+ ] + [
+ # Includes all the external dependencies from repositories.bzl
+ Label("@" + repo + "//:BUILD.bazel")
+ for repo in all_requirements
+ ],
+ ),
}
whl_library_attrs.update(**common_attrs)
diff --git a/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl b/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
index 2df60b0b52..c72187c7ee 100644
--- a/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
+++ b/python/pip_install/pip_repository_requirements_bzlmod.bzl.tmpl
@@ -1,7 +1,6 @@
"""Starlark representation of locked requirements.
-@generated by rules_python pip_parse repository rule
-from %%REQUIREMENTS_LOCK%%.
+@generated by rules_python pip.parse bzlmod extension.
"""
all_requirements = %%ALL_REQUIREMENTS%%
@@ -30,4 +29,19 @@ def entry_point(pkg, script = None):
"""
if not script:
script = pkg
- return "@@%%NAME%%_{}//:rules_python_wheel_entry_point_{}".format(_clean_name(pkg), script)
+ fail("""Please replace this instance of entry_point with the following:
+
+```
+load("@rules_python//python/entry_points:py_console_script_binary.bzl", "py_console_script_binary")
+
+py_console_script_binary(
+ name = "{pkg}",
+ pkg = "@%%{pkg_label}",
+ script = "{script}",
+)
+```
+""".format(
+ pkg = _clean_name(pkg),
+ pkg_label = "%%MACRO_TMPL%%".format(_clean_name(pkg), "pkg"),
+ script = script,
+ ))
diff --git a/python/pip_install/private/BUILD.bazel b/python/pip_install/private/BUILD.bazel
index 86b4b3d22c..2cc4cbd70d 100644
--- a/python/pip_install/private/BUILD.bazel
+++ b/python/pip_install/private/BUILD.bazel
@@ -1,3 +1,4 @@
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
load(":pip_install_utils.bzl", "srcs_module")
package(default_visibility = ["//:__subpackages__"])
@@ -22,3 +23,16 @@ srcs_module(
srcs = "//python/pip_install:py_srcs",
dest = ":srcs.bzl",
)
+
+bzl_library(
+ name = "generate_whl_library_build_bazel_bzl",
+ srcs = ["generate_whl_library_build_bazel.bzl"],
+ deps = [
+ "//python/private:normalize_name_bzl",
+ ],
+)
+
+bzl_library(
+ name = "srcs_bzl",
+ srcs = ["srcs.bzl"],
+)
diff --git a/python/pip_install/repositories.bzl b/python/pip_install/repositories.bzl
index 4b209b304c..b322a7007e 100644
--- a/python/pip_install/repositories.bzl
+++ b/python/pip_install/repositories.bzl
@@ -23,13 +23,13 @@ _RULE_DEPS = [
# START: maintained by 'bazel run //tools/private:update_pip_deps'
(
"pypi__build",
- "https://files.pythonhosted.org/packages/03/97/f58c723ff036a8d8b4d3115377c0a37ed05c1f68dd9a0d66dab5e82c5c1c/build-0.9.0-py3-none-any.whl",
- "38a7a2b7a0bdc61a42a0a67509d88c71ecfc37b393baba770fae34e20929ff69",
+ "https://files.pythonhosted.org/packages/58/91/17b00d5fac63d3dca605f1b8269ba3c65e98059e1fd99d00283e42a454f0/build-0.10.0-py3-none-any.whl",
+ "af266720050a66c893a6096a2f410989eeac74ff9a68ba194b3f6473e8e26171",
),
(
"pypi__click",
- "https://files.pythonhosted.org/packages/76/0a/b6c5f311e32aeb3b406e03c079ade51e905ea630fc19d1262a46249c1c86/click-8.0.1-py3-none-any.whl",
- "fba402a4a47334742d782209a7c79bc448911afe1149d07bdabdf480b3e2f4b6",
+ "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl",
+ "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28",
),
(
"pypi__colorama",
@@ -38,8 +38,8 @@ _RULE_DEPS = [
),
(
"pypi__importlib_metadata",
- "https://files.pythonhosted.org/packages/d7/31/74dcb59a601b95fce3b0334e8fc9db758f78e43075f22aeb3677dfb19f4c/importlib_metadata-1.4.0-py2.py3-none-any.whl",
- "bdd9b7c397c273bcc9a11d6629a38487cd07154fa255a467bf704cd2c258e359",
+ "https://files.pythonhosted.org/packages/cc/37/db7ba97e676af155f5fcb1a35466f446eadc9104e25b83366e8088c9c926/importlib_metadata-6.8.0-py3-none-any.whl",
+ "3ebb78df84a805d7698245025b975d9d67053cd94c79245ba4b3eb694abe68bb",
),
(
"pypi__installer",
@@ -48,13 +48,13 @@ _RULE_DEPS = [
),
(
"pypi__more_itertools",
- "https://files.pythonhosted.org/packages/bd/3f/c4b3dbd315e248f84c388bd4a72b131a29f123ecacc37ffb2b3834546e42/more_itertools-8.13.0-py3-none-any.whl",
- "c5122bffc5f104d37c1626b8615b511f3427aa5389b94d61e5ef8236bfbc3ddb",
+ "https://files.pythonhosted.org/packages/5a/cb/6dce742ea14e47d6f565589e859ad225f2a5de576d7696e0623b784e226b/more_itertools-10.1.0-py3-none-any.whl",
+ "64e0735fcfdc6f3464ea133afe8ea4483b1c5fe3a3d69852e6503b43a0b222e6",
),
(
"pypi__packaging",
- "https://files.pythonhosted.org/packages/8f/7b/42582927d281d7cb035609cd3a543ffac89b74f3f4ee8e1c50914bcb57eb/packaging-22.0-py3-none-any.whl",
- "957e2148ba0e1a3b282772e791ef1d8083648bc131c8ab0c1feba110ce1146c3",
+ "https://files.pythonhosted.org/packages/ab/c3/57f0601a2d4fe15de7a553c00adbc901425661bf048f2a22dfc500caf121/packaging-23.1-py3-none-any.whl",
+ "994793af429502c4ea2ebf6bf664629d07c1a9fe974af92966e4b8d2df7edc61",
),
(
"pypi__pep517",
@@ -63,18 +63,23 @@ _RULE_DEPS = [
),
(
"pypi__pip",
- "https://files.pythonhosted.org/packages/09/bd/2410905c76ee14c62baf69e3f4aa780226c1bbfc9485731ad018e35b0cb5/pip-22.3.1-py3-none-any.whl",
- "908c78e6bc29b676ede1c4d57981d490cb892eb45cd8c214ab6298125119e077",
+ "https://files.pythonhosted.org/packages/50/c2/e06851e8cc28dcad7c155f4753da8833ac06a5c704c109313b8d5a62968a/pip-23.2.1-py3-none-any.whl",
+ "7ccf472345f20d35bdc9d1841ff5f313260c2c33fe417f48c30ac46cccabf5be",
),
(
"pypi__pip_tools",
- "https://files.pythonhosted.org/packages/5e/e8/f6d7d1847c7351048da870417724ace5c4506e816b38db02f4d7c675c189/pip_tools-6.12.1-py3-none-any.whl",
- "f0c0c0ec57b58250afce458e2e6058b1f30a4263db895b7d72fd6311bf1dc6f7",
+ "https://files.pythonhosted.org/packages/e8/df/47e6267c6b5cdae867adbdd84b437393e6202ce4322de0a5e0b92960e1d6/pip_tools-7.3.0-py3-none-any.whl",
+ "8717693288720a8c6ebd07149c93ab0be1fced0b5191df9e9decd3263e20d85e",
+ ),
+ (
+ "pypi__pyproject_hooks",
+ "https://files.pythonhosted.org/packages/d5/ea/9ae603de7fbb3df820b23a70f6aff92bf8c7770043254ad8d2dc9d6bcba4/pyproject_hooks-1.0.0-py3-none-any.whl",
+ "283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8",
),
(
"pypi__setuptools",
- "https://files.pythonhosted.org/packages/7c/5b/3d92b9f0f7ca1645cba48c080b54fe7d8b1033a4e5720091d1631c4266db/setuptools-60.10.0-py3-none-any.whl",
- "782ef48d58982ddb49920c11a0c5c9c0b02e7d7d1c2ad0aa44e1a1e133051c96",
+ "https://files.pythonhosted.org/packages/4f/ab/0bcfebdfc3bfa8554b2b2c97a555569c4c1ebc74ea288741ea8326c51906/setuptools-68.1.2-py3-none-any.whl",
+ "3d8083eed2d13afc9426f227b24fd1659489ec107c0e86cec2ffdde5c92e790b",
),
(
"pypi__tomli",
@@ -83,13 +88,13 @@ _RULE_DEPS = [
),
(
"pypi__wheel",
- "https://files.pythonhosted.org/packages/bd/7c/d38a0b30ce22fc26ed7dbc087c6d00851fb3395e9d0dac40bec1f905030c/wheel-0.38.4-py3-none-any.whl",
- "b60533f3f5d530e971d6737ca6d58681ee434818fab630c83a734bb10c083ce8",
+ "https://files.pythonhosted.org/packages/b8/8b/31273bf66016be6ad22bb7345c37ff350276cfd46e389a0c2ac5da9d9073/wheel-0.41.2-py3-none-any.whl",
+ "75909db2664838d015e3d9139004ee16711748a52c8f336b52882266540215d8",
),
(
"pypi__zipp",
- "https://files.pythonhosted.org/packages/f4/50/cc72c5bcd48f6e98219fc4a88a5227e9e28b81637a99c49feba1d51f4d50/zipp-1.0.0-py2.py3-none-any.whl",
- "8dda78f06bd1674bd8720df8a50bb47b6e1233c503a4eed8e7810686bde37656",
+ "https://files.pythonhosted.org/packages/8c/08/d3006317aefe25ea79d3b76c9650afabaf6d63d1c8443b236e7405447503/zipp-3.16.2-py3-none-any.whl",
+ "679e51dd4403591b2d6838a48de3d283f3d188412a9782faadf845f298736ba0",
),
# END: maintained by 'bazel run //tools/private:update_pip_deps'
]
diff --git a/python/pip_install/requirements.bzl b/python/pip_install/requirements.bzl
index 84ee203ffd..a48718151f 100644
--- a/python/pip_install/requirements.bzl
+++ b/python/pip_install/requirements.bzl
@@ -90,20 +90,23 @@ def compile_pip_requirements(
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),
+ "--resolver=backtracking",
+ "--allow-unsafe",
] + (["--generate-hashes"] if generate_hashes else []) + extra_args
deps = [
requirement("build"),
requirement("click"),
requirement("colorama"),
+ requirement("importlib_metadata"),
+ requirement("more_itertools"),
requirement("pep517"),
requirement("pip"),
requirement("pip_tools"),
+ requirement("pyproject_hooks"),
requirement("setuptools"),
requirement("tomli"),
- requirement("importlib_metadata"),
requirement("zipp"),
- requirement("more_itertools"),
Label("//python/runfiles:runfiles"),
] + extra_deps
diff --git a/python/pip_install/tools/requirements.txt b/python/pip_install/tools/requirements.txt
index e8de11216e..bf9fe46afd 100755
--- a/python/pip_install/tools/requirements.txt
+++ b/python/pip_install/tools/requirements.txt
@@ -1,14 +1,14 @@
-build==0.9
-click==8.0.1
+build
+click
colorama
-importlib_metadata==1.4.0
+importlib_metadata
installer
-more_itertools==8.13.0
-packaging==22.0
+more_itertools
+packaging
pep517
-pip==22.3.1
-pip_tools==6.12.1
-setuptools==60.10
+pip
+pip_tools
+setuptools
tomli
-wheel==0.38.4
-zipp==1.0.0
+wheel
+zipp
diff --git a/python/pip_install/tools/wheel_installer/BUILD.bazel b/python/pip_install/tools/wheel_installer/BUILD.bazel
index 6360ca5c70..0eadcc25f6 100644
--- a/python/pip_install/tools/wheel_installer/BUILD.bazel
+++ b/python/pip_install/tools/wheel_installer/BUILD.bazel
@@ -9,6 +9,7 @@ py_library(
"wheel.py",
"wheel_installer.py",
],
+ visibility = ["//third_party/rules_pycross/pycross/private:__subpackages__"],
deps = [
requirement("installer"),
requirement("pip"),
diff --git a/python/private/BUILD.bazel b/python/private/BUILD.bazel
index 29b5a6c885..f6e3012edd 100644
--- a/python/private/BUILD.bazel
+++ b/python/private/BUILD.bazel
@@ -13,14 +13,24 @@
# limitations under the License.
load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+load("//python:py_binary.bzl", "py_binary")
+load("//python:py_library.bzl", "py_library")
load("//python:versions.bzl", "print_toolchains_checksums")
load(":stamp.bzl", "stamp_build_setting")
+package(
+ default_visibility = ["//:__subpackages__"],
+)
+
licenses(["notice"])
filegroup(
name = "distribution",
- srcs = glob(["**"]) + ["//python/private/proto:distribution"],
+ srcs = glob(["**"]) + [
+ "//python/private/common:distribution",
+ "//python/private/proto:distribution",
+ "//tools/build_defs/python/private:distribution",
+ ],
visibility = ["//python:__pkg__"],
)
@@ -38,34 +48,45 @@ filegroup(
)
bzl_library(
- name = "reexports_bzl",
- srcs = ["reexports.bzl"],
- visibility = [
- "//docs:__pkg__",
- "//python:__pkg__",
+ name = "autodetecting_toolchain_bzl",
+ srcs = ["autodetecting_toolchain.bzl"],
+ deps = [
+ "//python:py_runtime_bzl",
+ "//python:py_runtime_pair_bzl",
],
- deps = [":bazel_tools_bzl"],
)
bzl_library(
- name = "util_bzl",
- srcs = ["util.bzl"],
- visibility = [
- "//docs:__subpackages__",
- "//python:__subpackages__",
- ],
- deps = ["@bazel_skylib//lib:types"],
+ name = "bzlmod_enabled_bzl",
+ srcs = ["bzlmod_enabled.bzl"],
)
bzl_library(
- name = "which_bzl",
- srcs = ["which.bzl"],
- visibility = [
- "//docs:__subpackages__",
- "//python:__subpackages__",
+ name = "coverage_deps_bzl",
+ srcs = ["coverage_deps.bzl"],
+ deps = [
+ ":bazel_tools_bzl",
+ ":version_label_bzl",
],
)
+bzl_library(
+ name = "full_version_bzl",
+ srcs = ["full_version.bzl"],
+ deps = ["//python:versions_bzl"],
+)
+
+bzl_library(
+ name = "internal_config_repo_bzl",
+ srcs = ["internal_config_repo.bzl"],
+ deps = [":bzlmod_enabled_bzl"],
+)
+
+bzl_library(
+ name = "normalize_name_bzl",
+ srcs = ["normalize_name.bzl"],
+)
+
bzl_library(
name = "py_cc_toolchain_bzl",
srcs = [
@@ -88,15 +109,123 @@ bzl_library(
visibility = ["//python/cc:__pkg__"],
)
+bzl_library(
+ name = "py_console_script_binary_bzl",
+ srcs = [
+ "py_console_script_binary.bzl",
+ "py_console_script_gen.bzl",
+ ],
+ visibility = ["//python/entry_points:__pkg__"],
+ deps = [
+ "//python:py_binary_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_package_bzl",
+ srcs = ["py_package.bzl"],
+ visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
+ name = "py_runtime_pair_macro_bzl",
+ srcs = ["py_runtime_pair_macro.bzl"],
+ visibility = ["//:__subpackages__"],
+ deps = [":py_runtime_pair_rule_bzl"],
+)
+
+bzl_library(
+ name = "py_runtime_pair_rule_bzl",
+ srcs = ["py_runtime_pair_rule.bzl"],
+ deps = [
+ "//python:py_runtime_bzl",
+ "//python:py_runtime_info_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_wheel_bzl",
+ srcs = ["py_wheel.bzl"],
+ visibility = ["//:__subpackages__"],
+ deps = [
+ ":py_package_bzl",
+ ":stamp_bzl",
+ ],
+)
+
+bzl_library(
+ name = "reexports_bzl",
+ srcs = ["reexports.bzl"],
+ visibility = [
+ "//docs:__pkg__",
+ "//python:__pkg__",
+ ],
+ deps = [":bazel_tools_bzl"],
+)
+
+bzl_library(
+ name = "render_pkg_aliases_bzl",
+ srcs = ["render_pkg_aliases.bzl"],
+ deps = [
+ ":normalize_name_bzl",
+ ":text_util_bzl",
+ ":version_label_bzl",
+ ],
+)
+
+bzl_library(
+ name = "stamp_bzl",
+ srcs = ["stamp.bzl"],
+ visibility = ["//:__subpackages__"],
+)
+
+bzl_library(
+ name = "text_util_bzl",
+ srcs = ["text_util.bzl"],
+)
+
+bzl_library(
+ name = "toolchains_repo_bzl",
+ srcs = ["toolchains_repo.bzl"],
+ deps = [
+ ":which_bzl",
+ "//python:versions_bzl",
+ ],
+)
+
+bzl_library(
+ name = "util_bzl",
+ srcs = ["util.bzl"],
+ visibility = [
+ "//docs:__subpackages__",
+ "//python:__subpackages__",
+ ],
+ deps = ["@bazel_skylib//lib:types"],
+)
+
+bzl_library(
+ name = "version_label_bzl",
+ srcs = ["version_label.bzl"],
+)
+
+bzl_library(
+ name = "which_bzl",
+ srcs = ["which.bzl"],
+ visibility = [
+ "//docs:__subpackages__",
+ "//python:__subpackages__",
+ ],
+)
+
# @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",
+ # This set of sources is overly broad, but it's the only public
+ # target available across Bazel versions that has all the necessary
+ # sources.
+ "@bazel_tools//tools:bzl_srcs",
],
- visibility = ["//python:__pkg__"],
)
# Needed to define bzl_library targets for docgen. (We don't define the
@@ -107,15 +236,35 @@ exports_files(
"coverage.patch",
"py_package.bzl",
"py_wheel.bzl",
+ "py_wheel_normalize_pep440.bzl",
"reexports.bzl",
"stamp.bzl",
"util.bzl",
"py_cc_toolchain_rule.bzl",
],
- visibility = ["//docs:__pkg__"],
+ visibility = ["//:__subpackages__"],
)
# Used to determine the use of `--stamp` in Starlark rules
stamp_build_setting(name = "stamp")
print_toolchains_checksums(name = "print_toolchains_checksums")
+
+# Used for py_console_script_gen rule
+py_binary(
+ name = "py_console_script_gen_py",
+ srcs = ["py_console_script_gen.py"],
+ main = "py_console_script_gen.py",
+ visibility = [
+ "//visibility:public",
+ ],
+)
+
+py_library(
+ name = "py_console_script_gen_lib",
+ srcs = ["py_console_script_gen.py"],
+ imports = ["../.."],
+ visibility = [
+ "//tests/entry_points:__pkg__",
+ ],
+)
diff --git a/python/private/autodetecting_toolchain.bzl b/python/private/autodetecting_toolchain.bzl
new file mode 100644
index 0000000000..3caa5aa8ca
--- /dev/null
+++ b/python/private/autodetecting_toolchain.bzl
@@ -0,0 +1,70 @@
+# 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.
+
+"""Definitions related to the Python toolchain."""
+
+load("//python:py_runtime.bzl", "py_runtime")
+load("//python:py_runtime_pair.bzl", "py_runtime_pair")
+
+def define_autodetecting_toolchain(name):
+ """Defines the autodetecting Python toolchain.
+
+ Args:
+ name: The name of the toolchain to introduce. Must have value
+ "autodetecting_toolchain". This param is present only to make the
+ BUILD file more readable.
+ """
+ if name != "autodetecting_toolchain":
+ fail("Python autodetecting toolchain must be named " +
+ "'autodetecting_toolchain'")
+
+ # buildifier: disable=native-py
+ py_runtime(
+ name = "_autodetecting_py3_runtime",
+ interpreter = ":py3wrapper.sh",
+ python_version = "PY3",
+ stub_shebang = "#!/usr/bin/env python3",
+ visibility = ["//visibility:private"],
+ )
+
+ # This is a dummy runtime whose interpreter_path triggers the native rule
+ # logic to use the legacy behavior on Windows.
+ # TODO(#7844): Remove this target.
+ # buildifier: disable=native-py
+ py_runtime(
+ name = "_magic_sentinel_runtime",
+ interpreter_path = "/_magic_pyruntime_sentinel_do_not_use",
+ python_version = "PY3",
+ visibility = ["//visibility:private"],
+ )
+
+ py_runtime_pair(
+ name = "_autodetecting_py_runtime_pair",
+ py3_runtime = select({
+ # If we're on windows, inject the sentinel to tell native rule logic
+ # that we attempted to use the autodetecting toolchain and need to
+ # switch back to legacy behavior.
+ # TODO(#7844): Remove this hack.
+ "@platforms//os:windows": ":_magic_sentinel_runtime",
+ "//conditions:default": ":_autodetecting_py3_runtime",
+ }),
+ visibility = ["//visibility:public"],
+ )
+
+ native.toolchain(
+ name = name,
+ toolchain = ":_autodetecting_py_runtime_pair",
+ toolchain_type = ":toolchain_type",
+ visibility = ["//visibility:public"],
+ )
diff --git a/python/private/autodetecting_toolchain_interpreter.sh b/python/private/autodetecting_toolchain_interpreter.sh
new file mode 100644
index 0000000000..5c8a10d601
--- /dev/null
+++ b/python/private/autodetecting_toolchain_interpreter.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+
+# Don't set -e because we don't have robust trapping and printing of errors.
+set -u
+
+# We use /bin/sh rather than /bin/bash for portability. See discussion here:
+# https://groups.google.com/forum/?nomobile=true#!topic/bazel-dev/4Ql_7eDcLC0
+# We do lose the ability to set -o pipefail.
+
+FAILURE_HEADER="\
+Error occurred while attempting to use the default Python toolchain \
+(@rules_python//python:autodetecting_toolchain)."
+
+die() {
+ echo "$FAILURE_HEADER" 1>&2
+ echo "$1" 1>&2
+ exit 1
+}
+
+# We use `which` to locate the Python interpreter command on PATH. `command -v`
+# is another option, but it doesn't check whether the file it finds has the
+# executable bit.
+#
+# A tricky situation happens when this wrapper is invoked as part of running a
+# tool, e.g. passing a py_binary target to `ctx.actions.run()`. Bazel will unset
+# the PATH variable. Then the shell will see there's no PATH and initialize its
+# own, sometimes without exporting it. This causes `which` to fail and this
+# script to think there's no Python interpreter installed. To avoid this we
+# explicitly pass PATH to each `which` invocation. We can't just export PATH
+# because that would modify the environment seen by the final user Python
+# program.
+#
+# See also:
+#
+# https://github.com/bazelbuild/continuous-integration/issues/578
+# https://github.com/bazelbuild/bazel/issues/8414
+# https://github.com/bazelbuild/bazel/issues/8415
+
+# Try the "python3" command name first, then fall back on "python".
+PYTHON_BIN="$(PATH="$PATH" which python3 2> /dev/null)"
+if [ -z "${PYTHON_BIN:-}" ]; then
+ PYTHON_BIN="$(PATH="$PATH" which python 2>/dev/null)"
+fi
+if [ -z "${PYTHON_BIN:-}" ]; then
+ die "Neither 'python3' nor 'python' were found on the target \
+platform's PATH, which is:
+
+$PATH
+
+Please ensure an interpreter is available on this platform (and marked \
+executable), or else register an appropriate Python toolchain as per the \
+documentation for py_runtime_pair \
+(https://github.com/bazelbuild/rules_python/blob/master/docs/python.md#py_runtime_pair)."
+fi
+
+exec "$PYTHON_BIN" "$@"
+
diff --git a/python/private/common/BUILD.bazel b/python/private/common/BUILD.bazel
new file mode 100644
index 0000000000..f20e682e26
--- /dev/null
+++ b/python/private/common/BUILD.bazel
@@ -0,0 +1,204 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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")
+
+package(
+ default_visibility = ["//python:__subpackages__"],
+)
+
+bzl_library(
+ name = "attributes_bazel_bzl",
+ srcs = ["attributes_bazel.bzl"],
+)
+
+bzl_library(
+ name = "attributes_bzl",
+ srcs = ["attributes.bzl"],
+ deps = [
+ ":common_bzl",
+ ":providers_bzl",
+ ":py_internal_bzl",
+ ":semantics_bzl",
+ ],
+)
+
+bzl_library(
+ name = "cc_helper_bzl",
+ srcs = ["cc_helper.bzl"],
+ deps = [":py_internal_bzl"],
+)
+
+bzl_library(
+ name = "common_bazel_bzl",
+ srcs = ["common_bazel.bzl"],
+ deps = [
+ ":common_bzl",
+ ":providers_bzl",
+ ":py_internal_bzl",
+ "@bazel_skylib//lib:paths",
+ ],
+)
+
+bzl_library(
+ name = "common_bzl",
+ srcs = ["common.bzl"],
+ deps = [
+ ":cc_helper_bzl",
+ ":providers_bzl",
+ ":py_internal_bzl",
+ ":semantics_bzl",
+ ],
+)
+
+filegroup(
+ name = "distribution",
+ srcs = glob(["**"]),
+)
+
+bzl_library(
+ name = "providers_bzl",
+ srcs = ["providers.bzl"],
+ deps = [
+ ":semantics_bzl",
+ "@rules_python_internal//:rules_python_config_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_binary_macro_bazel_bzl",
+ srcs = ["py_binary_macro_bazel.bzl"],
+ deps = [
+ ":common_bzl",
+ ":py_binary_rule_bazel_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_binary_rule_bazel_bzl",
+ srcs = ["py_binary_rule_bazel.bzl"],
+ deps = [
+ ":attributes_bzl",
+ ":py_executable_bazel_bzl",
+ ":semantics_bzl",
+ "@bazel_skylib//lib:dicts",
+ ],
+)
+
+bzl_library(
+ name = "py_executable_bazel_bzl",
+ srcs = ["py_executable_bazel.bzl"],
+ deps = [
+ ":attributes_bazel_bzl",
+ ":common_bazel_bzl",
+ ":common_bzl",
+ ":providers_bzl",
+ ":py_executable_bzl",
+ ":py_internal_bzl",
+ ":semantics_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_executable_bzl",
+ srcs = ["py_executable.bzl"],
+ deps = [
+ ":attributes_bzl",
+ ":cc_helper_bzl",
+ ":common_bzl",
+ ":providers_bzl",
+ ":py_internal_bzl",
+ "@bazel_skylib//lib:dicts",
+ ],
+)
+
+bzl_library(
+ name = "py_internal_bzl",
+ srcs = ["py_internal.bzl"],
+ deps = ["@rules_python_internal//:py_internal_bzl"],
+)
+
+bzl_library(
+ name = "py_library_bzl",
+ srcs = ["py_library.bzl"],
+ deps = [
+ ":attributes_bzl",
+ ":common_bzl",
+ ":providers_bzl",
+ ":py_internal_bzl",
+ "@bazel_skylib//lib:dicts",
+ ],
+)
+
+bzl_library(
+ name = "py_library_macro_bazel_bzl",
+ srcs = ["py_library_macro_bazel.bzl"],
+ deps = [":py_library_rule_bazel_bzl"],
+)
+
+bzl_library(
+ name = "py_library_rule_bazel_bzl",
+ srcs = ["py_library_rule_bazel.bzl"],
+ deps = [
+ ":attributes_bazel_bzl",
+ ":common_bazel_bzl",
+ ":common_bzl",
+ ":py_library_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_runtime_macro_bzl",
+ srcs = ["py_runtime_macro.bzl"],
+ deps = [":py_runtime_rule_bzl"],
+)
+
+bzl_library(
+ name = "py_runtime_rule_bzl",
+ srcs = ["py_runtime_rule.bzl"],
+ deps = [
+ ":attributes_bzl",
+ ":common_bzl",
+ ":providers_bzl",
+ ":py_internal_bzl",
+ "@bazel_skylib//lib:dicts",
+ "@bazel_skylib//lib:paths",
+ ],
+)
+
+bzl_library(
+ name = "py_test_macro_bazel_bzl",
+ srcs = ["py_test_macro_bazel.bzl"],
+ deps = [
+ ":common_bazel_bzl",
+ ":py_test_rule_bazel_bzl",
+ ],
+)
+
+bzl_library(
+ name = "py_test_rule_bazel_bzl",
+ srcs = ["py_test_rule_bazel.bzl"],
+ deps = [
+ ":attributes_bzl",
+ ":common_bzl",
+ ":py_executable_bazel_bzl",
+ ":semantics_bzl",
+ "@bazel_skylib//lib:dicts",
+ ],
+)
+
+bzl_library(
+ name = "semantics_bzl",
+ srcs = ["semantics.bzl"],
+)
diff --git a/python/private/common/attributes.bzl b/python/private/common/attributes.bzl
new file mode 100644
index 0000000000..6e184c0c8f
--- /dev/null
+++ b/python/private/common/attributes.bzl
@@ -0,0 +1,242 @@
+# 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.
+"""Attributes for Python rules."""
+
+load(":common.bzl", "union_attrs")
+load(":providers.bzl", "PyInfo")
+load(":py_internal.bzl", "py_internal")
+load(
+ ":semantics.bzl",
+ "DEPS_ATTR_ALLOW_RULES",
+ "PLATFORMS_LOCATION",
+ "SRCS_ATTR_ALLOW_FILES",
+ "TOOLS_REPO",
+)
+
+# TODO: Load CcInfo from rules_cc
+_CcInfo = CcInfo
+_PackageSpecificationInfo = getattr(py_internal, "PackageSpecificationInfo", None)
+
+_STAMP_VALUES = [-1, 0, 1]
+
+def create_stamp_attr(**kwargs):
+ return {"stamp": attr.int(values = _STAMP_VALUES, **kwargs)}
+
+def create_srcs_attr(*, mandatory):
+ return {
+ "srcs": attr.label_list(
+ # Google builds change the set of allowed files.
+ allow_files = SRCS_ATTR_ALLOW_FILES,
+ mandatory = mandatory,
+ # Necessary for --compile_one_dependency to work.
+ flags = ["DIRECT_COMPILE_TIME_INPUT"],
+ ),
+ }
+
+SRCS_VERSION_ALL_VALUES = ["PY2", "PY2ONLY", "PY2AND3", "PY3", "PY3ONLY"]
+SRCS_VERSION_NON_CONVERSION_VALUES = ["PY2AND3", "PY2ONLY", "PY3ONLY"]
+
+def create_srcs_version_attr(values):
+ return {
+ "srcs_version": attr.string(
+ default = "PY2AND3",
+ values = values,
+ ),
+ }
+
+def copy_common_binary_kwargs(kwargs):
+ return {
+ key: kwargs[key]
+ for key in BINARY_ATTR_NAMES
+ if key in kwargs
+ }
+
+def copy_common_test_kwargs(kwargs):
+ return {
+ key: kwargs[key]
+ for key in TEST_ATTR_NAMES
+ if key in kwargs
+ }
+
+CC_TOOLCHAIN = {
+ # NOTE: The `cc_helper.find_cpp_toolchain()` function expects the attribute
+ # name to be this name.
+ "_cc_toolchain": attr.label(default = "@" + TOOLS_REPO + "//tools/cpp:current_cc_toolchain"),
+}
+
+# The common "data" attribute definition.
+DATA_ATTRS = {
+ # NOTE: The "flags" attribute is deprecated, but there isn't an alternative
+ # way to specify that constraints should be ignored.
+ "data": attr.label_list(
+ allow_files = True,
+ flags = ["SKIP_CONSTRAINTS_OVERRIDE"],
+ ),
+}
+
+def _create_native_rules_allowlist_attrs():
+ if py_internal:
+ # The fragment and name are validated when configuration_field is called
+ default = configuration_field(
+ fragment = "py",
+ name = "native_rules_allowlist",
+ )
+
+ # A None provider isn't allowed
+ providers = [_PackageSpecificationInfo]
+ else:
+ default = None
+ providers = []
+
+ return {
+ "_native_rules_allowlist": attr.label(
+ default = default,
+ providers = providers,
+ ),
+ }
+
+NATIVE_RULES_ALLOWLIST_ATTRS = _create_native_rules_allowlist_attrs()
+
+# Attributes common to all rules.
+COMMON_ATTRS = union_attrs(
+ DATA_ATTRS,
+ NATIVE_RULES_ALLOWLIST_ATTRS,
+ {
+ # NOTE: This attribute is deprecated and slated for removal.
+ "distribs": attr.string_list(),
+ # TODO(b/148103851): This attribute is deprecated and slated for
+ # removal.
+ # NOTE: The license attribute is missing in some Java integration tests,
+ # so fallback to a regular string_list for that case.
+ # buildifier: disable=attr-license
+ "licenses": attr.license() if hasattr(attr, "license") else attr.string_list(),
+ },
+ allow_none = True,
+)
+
+# Attributes common to rules accepting Python sources and deps.
+PY_SRCS_ATTRS = union_attrs(
+ {
+ "deps": attr.label_list(
+ providers = [[PyInfo], [_CcInfo]],
+ # TODO(b/228692666): Google-specific; remove these allowances once
+ # the depot is cleaned up.
+ allow_rules = DEPS_ATTR_ALLOW_RULES,
+ ),
+ # Required attribute, but details vary by rule.
+ # Use create_srcs_attr to create one.
+ "srcs": None,
+ # NOTE: In Google, this attribute is deprecated, and can only
+ # effectively be PY3 or PY3ONLY. Externally, with Bazel, this attribute
+ # has a separate story.
+ # Required attribute, but the details vary by rule.
+ # Use create_srcs_version_attr to create one.
+ "srcs_version": None,
+ },
+ allow_none = True,
+)
+
+# Attributes specific to Python executable-equivalent rules. Such rules may not
+# accept Python sources (e.g. some packaged-version of a py_test/py_binary), but
+# still accept Python source-agnostic settings.
+AGNOSTIC_EXECUTABLE_ATTRS = union_attrs(
+ DATA_ATTRS,
+ {
+ "env": attr.string_dict(
+ doc = """\
+Dictionary of strings; optional; values are subject to `$(location)` and "Make
+variable" substitution.
+
+Specifies additional environment variables to set when the target is executed by
+`test` or `run`.
+""",
+ ),
+ # The value is required, but varies by rule and/or rule type. Use
+ # create_stamp_attr to create one.
+ "stamp": None,
+ },
+ allow_none = True,
+)
+
+# Attributes specific to Python test-equivalent executable rules. Such rules may
+# not accept Python sources (e.g. some packaged-version of a py_test/py_binary),
+# but still accept Python source-agnostic settings.
+AGNOSTIC_TEST_ATTRS = union_attrs(
+ AGNOSTIC_EXECUTABLE_ATTRS,
+ # Tests have stamping disabled by default.
+ create_stamp_attr(default = 0),
+ {
+ "env_inherit": attr.string_list(
+ doc = """\
+List of strings; optional
+
+Specifies additional environment variables to inherit from the external
+environment when the test is executed by bazel test.
+""",
+ ),
+ # TODO(b/176993122): Remove when Bazel automatically knows to run on darwin.
+ "_apple_constraints": attr.label_list(
+ default = [
+ PLATFORMS_LOCATION + "/os:ios",
+ PLATFORMS_LOCATION + "/os:macos",
+ PLATFORMS_LOCATION + "/os:tvos",
+ PLATFORMS_LOCATION + "/os:visionos",
+ PLATFORMS_LOCATION + "/os:watchos",
+ ],
+ ),
+ },
+)
+
+# Attributes specific to Python binary-equivalent executable rules. Such rules may
+# not accept Python sources (e.g. some packaged-version of a py_test/py_binary),
+# but still accept Python source-agnostic settings.
+AGNOSTIC_BINARY_ATTRS = union_attrs(
+ AGNOSTIC_EXECUTABLE_ATTRS,
+ create_stamp_attr(default = -1),
+)
+
+# Attribute names common to all Python rules
+COMMON_ATTR_NAMES = [
+ "compatible_with",
+ "deprecation",
+ "distribs", # NOTE: Currently common to all rules, but slated for removal
+ "exec_compatible_with",
+ "exec_properties",
+ "features",
+ "restricted_to",
+ "tags",
+ "target_compatible_with",
+ # NOTE: The testonly attribute requires careful handling: None/unset means
+ # to use the `package(default_testonly`) value, which isn't observable
+ # during the loading phase.
+ "testonly",
+ "toolchains",
+ "visibility",
+] + COMMON_ATTRS.keys()
+
+# Attribute names common to all test=True rules
+TEST_ATTR_NAMES = COMMON_ATTR_NAMES + [
+ "args",
+ "size",
+ "timeout",
+ "flaky",
+ "shard_count",
+ "local",
+] + AGNOSTIC_TEST_ATTRS.keys()
+
+# Attribute names common to all executable=True rules
+BINARY_ATTR_NAMES = COMMON_ATTR_NAMES + [
+ "args",
+ "output_licenses", # NOTE: Common to all rules, but slated for removal
+] + AGNOSTIC_BINARY_ATTRS.keys()
diff --git a/python/private/common/attributes_bazel.bzl b/python/private/common/attributes_bazel.bzl
new file mode 100644
index 0000000000..f87245d6ff
--- /dev/null
+++ b/python/private/common/attributes_bazel.bzl
@@ -0,0 +1,30 @@
+# 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.
+"""Attributes specific to the Bazel implementation of the Python rules."""
+
+IMPORTS_ATTRS = {
+ "imports": attr.string_list(
+ doc = """
+List of import directories to be added to the PYTHONPATH.
+
+Subject to "Make variable" substitution. These import directories will be added
+for this rule and all rules that depend on it (note: not the rules this rule
+depends on. Each directory will be added to `PYTHONPATH` by `py_binary` rules
+that depend on this rule. The strings are repo-runfiles-root relative,
+
+Absolute paths (paths that start with `/`) and paths that references a path
+above the execution root are not allowed and will result in an error.
+""",
+ ),
+}
diff --git a/python/private/common/cc_helper.bzl b/python/private/common/cc_helper.bzl
new file mode 100644
index 0000000000..552b42eae8
--- /dev/null
+++ b/python/private/common/cc_helper.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.
+"""PYTHON RULE IMPLEMENTATION ONLY: Do not use outside of the rule implementations and their tests.
+
+Adapter for accessing Bazel's internal cc_helper.
+
+These may change at any time and are closely coupled to the rule implementation.
+"""
+
+load(":py_internal.bzl", "py_internal")
+
+cc_helper = getattr(py_internal, "cc_helper", None)
diff --git a/python/private/common/common.bzl b/python/private/common/common.bzl
new file mode 100644
index 0000000000..bffbf6f0cf
--- /dev/null
+++ b/python/private/common/common.bzl
@@ -0,0 +1,526 @@
+# 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.
+"""Various things common to Bazel and Google rule implementations."""
+
+load(":cc_helper.bzl", "cc_helper")
+load(":providers.bzl", "PyInfo")
+load(":py_internal.bzl", "py_internal")
+load(
+ ":semantics.bzl",
+ "NATIVE_RULES_MIGRATION_FIX_CMD",
+ "NATIVE_RULES_MIGRATION_HELP_URL",
+ "TOOLS_REPO",
+)
+
+_testing = testing
+_platform_common = platform_common
+_coverage_common = coverage_common
+_py_builtins = py_internal
+PackageSpecificationInfo = getattr(py_internal, "PackageSpecificationInfo", None)
+
+TOOLCHAIN_TYPE = "@" + TOOLS_REPO + "//tools/python:toolchain_type"
+
+# Extensions without the dot
+_PYTHON_SOURCE_EXTENSIONS = ["py"]
+
+# NOTE: Must stay in sync with the value used in rules_python
+_MIGRATION_TAG = "__PYTHON_RULES_MIGRATION_DO_NOT_USE_WILL_BREAK__"
+
+def create_binary_semantics_struct(
+ *,
+ create_executable,
+ get_cc_details_for_binary,
+ get_central_uncachable_version_file,
+ get_coverage_deps,
+ get_debugger_deps,
+ get_extra_common_runfiles_for_binary,
+ get_extra_providers,
+ get_extra_write_build_data_env,
+ get_interpreter_path,
+ get_imports,
+ get_native_deps_dso_name,
+ get_native_deps_user_link_flags,
+ get_stamp_flag,
+ maybe_precompile,
+ should_build_native_deps_dso,
+ should_create_init_files,
+ should_include_build_data):
+ """Helper to ensure a semantics struct has all necessary fields.
+
+ Call this instead of a raw call to `struct(...)`; it'll help ensure all
+ the necessary functions are being correctly provided.
+
+ Args:
+ create_executable: Callable; creates a binary's executable output. See
+ py_executable.bzl#py_executable_base_impl for details.
+ get_cc_details_for_binary: Callable that returns a `CcDetails` struct; see
+ `create_cc_detail_struct`.
+ get_central_uncachable_version_file: Callable that returns an optional
+ Artifact; this artifact is special: it is never cached and is a copy
+ of `ctx.version_file`; see py_builtins.copy_without_caching
+ get_coverage_deps: Callable that returns a list of Targets for making
+ coverage work; only called if coverage is enabled.
+ get_debugger_deps: Callable that returns a list of Targets that provide
+ custom debugger support; only called for target-configuration.
+ get_extra_common_runfiles_for_binary: Callable that returns a runfiles
+ object of extra runfiles a binary should include.
+ get_extra_providers: Callable that returns extra providers; see
+ py_executable.bzl#_create_providers for details.
+ get_extra_write_build_data_env: Callable that returns a dict[str, str]
+ of additional environment variable to pass to build data generation.
+ get_interpreter_path: Callable that returns an optional string, which is
+ the path to the Python interpreter to use for running the binary.
+ get_imports: Callable that returns a list of the target's import
+ paths (from the `imports` attribute, so just the target's own import
+ path strings, not from dependencies).
+ get_native_deps_dso_name: Callable that returns a string, which is the
+ basename (with extension) of the native deps DSO library.
+ get_native_deps_user_link_flags: Callable that returns a list of strings,
+ which are any extra linker flags to pass onto the native deps DSO
+ linking action.
+ get_stamp_flag: Callable that returns bool of if the --stamp flag was
+ enabled or not.
+ maybe_precompile: Callable that may optional precompile the input `.py`
+ sources and returns the full set of desired outputs derived from
+ the source files (e.g., both py and pyc, only one of them, etc).
+ should_build_native_deps_dso: Callable that returns bool; True if
+ building a native deps DSO is supported, False if not.
+ should_create_init_files: Callable that returns bool; True if
+ `__init__.py` files should be generated, False if not.
+ should_include_build_data: Callable that returns bool; True if
+ build data should be generated, False if not.
+ Returns:
+ A "BinarySemantics" struct.
+ """
+ return struct(
+ # keep-sorted
+ create_executable = create_executable,
+ get_cc_details_for_binary = get_cc_details_for_binary,
+ get_central_uncachable_version_file = get_central_uncachable_version_file,
+ get_coverage_deps = get_coverage_deps,
+ get_debugger_deps = get_debugger_deps,
+ get_extra_common_runfiles_for_binary = get_extra_common_runfiles_for_binary,
+ get_extra_providers = get_extra_providers,
+ get_extra_write_build_data_env = get_extra_write_build_data_env,
+ get_imports = get_imports,
+ get_interpreter_path = get_interpreter_path,
+ get_native_deps_dso_name = get_native_deps_dso_name,
+ get_native_deps_user_link_flags = get_native_deps_user_link_flags,
+ get_stamp_flag = get_stamp_flag,
+ maybe_precompile = maybe_precompile,
+ should_build_native_deps_dso = should_build_native_deps_dso,
+ should_create_init_files = should_create_init_files,
+ should_include_build_data = should_include_build_data,
+ )
+
+def create_library_semantics_struct(
+ *,
+ get_cc_info_for_library,
+ get_imports,
+ maybe_precompile):
+ """Create a `LibrarySemantics` struct.
+
+ Call this instead of a raw call to `struct(...)`; it'll help ensure all
+ the necessary functions are being correctly provided.
+
+ Args:
+ get_cc_info_for_library: Callable that returns a CcInfo for the library;
+ see py_library_impl for arg details.
+ get_imports: Callable; see create_binary_semantics_struct.
+ maybe_precompile: Callable; see create_binary_semantics_struct.
+ Returns:
+ a `LibrarySemantics` struct.
+ """
+ return struct(
+ # keep sorted
+ get_cc_info_for_library = get_cc_info_for_library,
+ get_imports = get_imports,
+ maybe_precompile = maybe_precompile,
+ )
+
+def create_cc_details_struct(
+ *,
+ cc_info_for_propagating,
+ cc_info_for_self_link,
+ cc_info_with_extra_link_time_libraries,
+ extra_runfiles,
+ cc_toolchain):
+ """Creates a CcDetails struct.
+
+ Args:
+ cc_info_for_propagating: CcInfo that is propagated out of the target
+ by returning it within a PyCcLinkParamsProvider object.
+ cc_info_for_self_link: CcInfo that is used when linking for the
+ binary (or its native deps DSO) itself. This may include extra
+ information that isn't propagating (e.g. a custom malloc)
+ cc_info_with_extra_link_time_libraries: CcInfo of extra link time
+ libraries that MUST come after `cc_info_for_self_link` (or possibly
+ always last; not entirely clear) when passed to
+ `link.linking_contexts`.
+ extra_runfiles: runfiles of extra files needed at runtime, usually as
+ part of `cc_info_with_extra_link_time_libraries`; should be added to
+ runfiles.
+ cc_toolchain: CcToolchain that should be used when building.
+
+ Returns:
+ A `CcDetails` struct.
+ """
+ return struct(
+ cc_info_for_propagating = cc_info_for_propagating,
+ cc_info_for_self_link = cc_info_for_self_link,
+ cc_info_with_extra_link_time_libraries = cc_info_with_extra_link_time_libraries,
+ extra_runfiles = extra_runfiles,
+ cc_toolchain = cc_toolchain,
+ )
+
+def create_executable_result_struct(*, extra_files_to_build, output_groups):
+ """Creates a `CreateExecutableResult` struct.
+
+ This is the return value type of the semantics create_executable function.
+
+ Args:
+ extra_files_to_build: depset of File; additional files that should be
+ included as default outputs.
+ output_groups: dict[str, depset[File]]; additional output groups that
+ should be returned.
+
+ Returns:
+ A `CreateExecutableResult` struct.
+ """
+ return struct(
+ extra_files_to_build = extra_files_to_build,
+ output_groups = output_groups,
+ )
+
+def union_attrs(*attr_dicts, allow_none = False):
+ """Helper for combining and building attriute dicts for rules.
+
+ Similar to dict.update, except:
+ * Duplicate keys raise an error if they aren't equal. This is to prevent
+ unintentionally replacing an attribute with a potentially incompatible
+ definition.
+ * None values are special: They mean the attribute is required, but the
+ value should be provided by another attribute dict (depending on the
+ `allow_none` arg).
+ Args:
+ *attr_dicts: The dicts to combine.
+ allow_none: bool, if True, then None values are allowed. If False,
+ then one of `attrs_dicts` must set a non-None value for keys
+ with a None value.
+
+ Returns:
+ dict of attributes.
+ """
+ result = {}
+ missing = {}
+ for attr_dict in attr_dicts:
+ for attr_name, value in attr_dict.items():
+ if value == None and not allow_none:
+ if attr_name not in result:
+ missing[attr_name] = None
+ else:
+ if attr_name in missing:
+ missing.pop(attr_name)
+
+ if attr_name not in result or result[attr_name] == None:
+ result[attr_name] = value
+ elif value != None and result[attr_name] != value:
+ fail("Duplicate attribute name: '{}': existing={}, new={}".format(
+ attr_name,
+ result[attr_name],
+ value,
+ ))
+
+ # Else, they're equal, so do nothing. This allows merging dicts
+ # that both define the same key from a common place.
+
+ if missing and not allow_none:
+ fail("Required attributes missing: " + csv(missing.keys()))
+ return result
+
+def csv(values):
+ """Convert a list of strings to comma separated value string."""
+ return ", ".join(sorted(values))
+
+def filter_to_py_srcs(srcs):
+ """Filters .py files from the given list of files"""
+
+ # TODO(b/203567235): Get the set of recognized extensions from
+ # elsewhere, as there may be others. e.g. Bazel recognizes .py3
+ # as a valid extension.
+ return [f for f in srcs if f.extension == "py"]
+
+def collect_imports(ctx, semantics):
+ return depset(direct = semantics.get_imports(ctx), transitive = [
+ dep[PyInfo].imports
+ for dep in ctx.attr.deps
+ if PyInfo in dep
+ ])
+
+def collect_runfiles(ctx, files):
+ """Collects the necessary files from the rule's context.
+
+ This presumes the ctx is for a py_binary, py_test, or py_library rule.
+
+ Args:
+ ctx: rule ctx
+ files: depset of extra files to include in the runfiles.
+ Returns:
+ runfiles necessary for the ctx's target.
+ """
+ return ctx.runfiles(
+ transitive_files = files,
+ # This little arg carries a lot of weight, but because Starlark doesn't
+ # have a way to identify if a target is just a File, the equivalent
+ # logic can't be re-implemented in pure-Starlark.
+ #
+ # Under the hood, it calls the Java `Runfiles#addRunfiles(ctx,
+ # DEFAULT_RUNFILES)` method, which is the what the Java implementation
+ # of the Python rules originally did, and the details of how that method
+ # works have become relied on in various ways. Specifically, what it
+ # does is visit the srcs, deps, and data attributes in the following
+ # ways:
+ #
+ # For each target in the "data" attribute...
+ # If the target is a File, then add that file to the runfiles.
+ # Otherwise, add the target's **data runfiles** to the runfiles.
+ #
+ # Note that, contray to best practice, the default outputs of the
+ # targets in `data` are *not* added, nor are the default runfiles.
+ #
+ # This ends up being important for several reasons, some of which are
+ # specific to Google-internal features of the rules.
+ # * For Python executables, we have to use `data_runfiles` to avoid
+ # conflicts for the build data files. Such files have
+ # target-specific content, but uses a fixed location, so if a
+ # binary has another binary in `data`, and both try to specify a
+ # file for that file path, then a warning is printed and an
+ # arbitrary one will be used.
+ # * For rules with _entirely_ different sets of files in data runfiles
+ # vs default runfiles vs default outputs. For example,
+ # proto_library: documented behavior of this rule is that putting it
+ # in the `data` attribute will cause the transitive closure of
+ # `.proto` source files to be included. This set of sources is only
+ # in the `data_runfiles` (`default_runfiles` is empty).
+ # * For rules with a _subset_ of files in data runfiles. For example,
+ # a certain Google rule used for packaging arbitrary binaries will
+ # generate multiple versions of a binary (e.g. different archs,
+ # stripped vs un-stripped, etc) in its default outputs, but only
+ # one of them in the runfiles; this helps avoid large, unused
+ # binaries contributing to remote executor input limits.
+ #
+ # Unfortunately, the above behavior also results in surprising behavior
+ # in some cases. For example, simple custom rules that only return their
+ # files in their default outputs won't have their files included. Such
+ # cases must either return their files in runfiles, or use `filegroup()`
+ # which will do so for them.
+ #
+ # For each target in "srcs" and "deps"...
+ # Add the default runfiles of the target to the runfiles. While this
+ # is desirable behavior, it also ends up letting a `py_library`
+ # be put in `srcs` and still mostly work.
+ # TODO(b/224640180): Reject py_library et al rules in srcs.
+ collect_default = True,
+ )
+
+def create_py_info(ctx, *, direct_sources, imports):
+ """Create PyInfo provider.
+
+ Args:
+ ctx: rule ctx.
+ direct_sources: depset of Files; the direct, raw `.py` sources for the
+ target. This should only be Python source files. It should not
+ include pyc files.
+ imports: depset of strings; the import path values to propagate.
+
+ Returns:
+ A tuple of the PyInfo instance and a depset of the
+ transitive sources collected from dependencies (the latter is only
+ necessary for deprecated extra actions support).
+ """
+ uses_shared_libraries = False
+ has_py2_only_sources = ctx.attr.srcs_version in ("PY2", "PY2ONLY")
+ has_py3_only_sources = ctx.attr.srcs_version in ("PY3", "PY3ONLY")
+ transitive_sources_depsets = [] # list of depsets
+ transitive_sources_files = [] # list of Files
+ for target in ctx.attr.deps:
+ # PyInfo may not be present for e.g. cc_library rules.
+ if PyInfo in target:
+ info = target[PyInfo]
+ transitive_sources_depsets.append(info.transitive_sources)
+ uses_shared_libraries = uses_shared_libraries or info.uses_shared_libraries
+ has_py2_only_sources = has_py2_only_sources or info.has_py2_only_sources
+ has_py3_only_sources = has_py3_only_sources or info.has_py3_only_sources
+ else:
+ # TODO(b/228692666): Remove this once non-PyInfo targets are no
+ # longer supported in `deps`.
+ files = target.files.to_list()
+ for f in files:
+ if f.extension == "py":
+ transitive_sources_files.append(f)
+ uses_shared_libraries = (
+ uses_shared_libraries or
+ cc_helper.is_valid_shared_library_artifact(f)
+ )
+ deps_transitive_sources = depset(
+ direct = transitive_sources_files,
+ transitive = transitive_sources_depsets,
+ )
+
+ # We only look at data to calculate uses_shared_libraries, if it's already
+ # true, then we don't need to waste time looping over it.
+ if not uses_shared_libraries:
+ # Similar to the above, except we only calculate uses_shared_libraries
+ for target in ctx.attr.data:
+ # TODO(b/234730058): Remove checking for PyInfo in data once depot
+ # cleaned up.
+ if PyInfo in target:
+ info = target[PyInfo]
+ uses_shared_libraries = info.uses_shared_libraries
+ else:
+ files = target.files.to_list()
+ for f in files:
+ uses_shared_libraries = cc_helper.is_valid_shared_library_artifact(f)
+ if uses_shared_libraries:
+ break
+ if uses_shared_libraries:
+ break
+
+ # TODO(b/203567235): Set `uses_shared_libraries` field, though the Bazel
+ # docs indicate it's unused in Bazel and may be removed.
+ py_info = PyInfo(
+ transitive_sources = depset(
+ transitive = [deps_transitive_sources, direct_sources],
+ ),
+ imports = imports,
+ # NOTE: This isn't strictly correct, but with Python 2 gone,
+ # the srcs_version logic is largely defunct, so shouldn't matter in
+ # practice.
+ has_py2_only_sources = has_py2_only_sources,
+ has_py3_only_sources = has_py3_only_sources,
+ uses_shared_libraries = uses_shared_libraries,
+ )
+ return py_info, deps_transitive_sources
+
+def create_instrumented_files_info(ctx):
+ return _coverage_common.instrumented_files_info(
+ ctx,
+ source_attributes = ["srcs"],
+ dependency_attributes = ["deps", "data"],
+ extensions = _PYTHON_SOURCE_EXTENSIONS,
+ )
+
+def create_output_group_info(transitive_sources, extra_groups):
+ return OutputGroupInfo(
+ compilation_prerequisites_INTERNAL_ = transitive_sources,
+ compilation_outputs = transitive_sources,
+ **extra_groups
+ )
+
+def maybe_add_test_execution_info(providers, ctx):
+ """Adds ExecutionInfo, if necessary for proper test execution.
+
+ Args:
+ providers: Mutable list of providers; may have ExecutionInfo
+ provider appended.
+ ctx: Rule ctx.
+ """
+
+ # When built for Apple platforms, require the execution to be on a Mac.
+ # TODO(b/176993122): Remove when bazel automatically knows to run on darwin.
+ if target_platform_has_any_constraint(ctx, ctx.attr._apple_constraints):
+ providers.append(_testing.ExecutionInfo({"requires-darwin": ""}))
+
+_BOOL_TYPE = type(True)
+
+def is_bool(v):
+ return type(v) == _BOOL_TYPE
+
+def target_platform_has_any_constraint(ctx, constraints):
+ """Check if target platform has any of a list of constraints.
+
+ Args:
+ ctx: rule context.
+ constraints: label_list of constraints.
+
+ Returns:
+ True if target platform has at least one of the constraints.
+ """
+ for constraint in constraints:
+ constraint_value = constraint[_platform_common.ConstraintValueInfo]
+ if ctx.target_platform_has_constraint(constraint_value):
+ return True
+ return False
+
+def check_native_allowed(ctx):
+ """Check if the usage of the native rule is allowed.
+
+ Args:
+ ctx: rule context to check
+ """
+ if not ctx.fragments.py.disallow_native_rules:
+ return
+
+ if _MIGRATION_TAG in ctx.attr.tags:
+ return
+
+ # NOTE: The main repo name is empty in *labels*, but not in
+ # ctx.workspace_name
+ is_main_repo = not bool(ctx.label.workspace_name)
+ if is_main_repo:
+ check_label = ctx.label
+ else:
+ # package_group doesn't allow @repo syntax, so we work around that
+ # by prefixing external repos with a fake package path. This also
+ # makes it easy to enable or disable all external repos.
+ check_label = Label("@//__EXTERNAL_REPOS__/{workspace}/{package}".format(
+ workspace = ctx.label.workspace_name,
+ package = ctx.label.package,
+ ))
+ allowlist = ctx.attr._native_rules_allowlist
+ if allowlist:
+ allowed = ctx.attr._native_rules_allowlist[PackageSpecificationInfo].contains(check_label)
+ allowlist_help = str(allowlist.label).replace("@//", "//")
+ else:
+ allowed = False
+ allowlist_help = ("no allowlist specified; all disallowed; specify one " +
+ "with --python_native_rules_allowlist")
+ if not allowed:
+ if ctx.attr.generator_function:
+ generator = "{generator_function}(name={generator_name}) in {generator_location}".format(
+ generator_function = ctx.attr.generator_function,
+ generator_name = ctx.attr.generator_name,
+ generator_location = ctx.attr.generator_location,
+ )
+ else:
+ generator = "No generator (called directly in BUILD file)"
+
+ msg = (
+ "{target} not allowed to use native.{rule}\n" +
+ "Generated by: {generator}\n" +
+ "Allowlist: {allowlist}\n" +
+ "Migrate to using @rules_python, see {help_url}\n" +
+ "FIXCMD: {fix_cmd} --target={target} --rule={rule} " +
+ "--generator_name={generator_name} --location={generator_location}"
+ )
+ fail(msg.format(
+ target = str(ctx.label).replace("@//", "//"),
+ rule = _py_builtins.get_rule_name(ctx),
+ generator = generator,
+ allowlist = allowlist_help,
+ generator_name = ctx.attr.generator_name,
+ generator_location = ctx.attr.generator_location,
+ help_url = NATIVE_RULES_MIGRATION_HELP_URL,
+ fix_cmd = NATIVE_RULES_MIGRATION_FIX_CMD,
+ ))
diff --git a/python/private/common/common_bazel.bzl b/python/private/common/common_bazel.bzl
new file mode 100644
index 0000000000..7277337849
--- /dev/null
+++ b/python/private/common/common_bazel.bzl
@@ -0,0 +1,109 @@
+# 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.
+"""Common functions that are specific to Bazel rule implementation"""
+
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load(":common.bzl", "is_bool")
+load(":providers.bzl", "PyCcLinkParamsProvider")
+load(":py_internal.bzl", "py_internal")
+
+# TODO: Load cc_common from rules_cc
+_cc_common = cc_common
+
+# TODO: Load CcInfo from rules_cc
+_CcInfo = CcInfo
+
+_py_builtins = py_internal
+
+def collect_cc_info(ctx, extra_deps = []):
+ """Collect C++ information from dependencies for Bazel.
+
+ Args:
+ ctx: Rule ctx; must have `deps` attribute.
+ extra_deps: list of Target to also collect C+ information from.
+
+ Returns:
+ CcInfo provider of merged information.
+ """
+ deps = ctx.attr.deps
+ if extra_deps:
+ deps = list(deps)
+ deps.extend(extra_deps)
+ cc_infos = []
+ for dep in deps:
+ if _CcInfo in dep:
+ cc_infos.append(dep[_CcInfo])
+
+ if PyCcLinkParamsProvider in dep:
+ cc_infos.append(dep[PyCcLinkParamsProvider].cc_info)
+
+ return _cc_common.merge_cc_infos(cc_infos = cc_infos)
+
+def maybe_precompile(ctx, srcs):
+ """Computes all the outputs (maybe precompiled) from the input srcs.
+
+ See create_binary_semantics_struct for details about this function.
+
+ Args:
+ ctx: Rule ctx.
+ srcs: List of Files; the inputs to maybe precompile.
+
+ Returns:
+ List of Files; the desired output files derived from the input sources.
+ """
+ _ = ctx # @unused
+
+ # Precompilation isn't implemented yet, so just return srcs as-is
+ return srcs
+
+def get_imports(ctx):
+ """Gets the imports from a rule's `imports` attribute.
+
+ See create_binary_semantics_struct for details about this function.
+
+ Args:
+ ctx: Rule ctx.
+
+ Returns:
+ List of strings.
+ """
+ prefix = "{}/{}".format(
+ ctx.workspace_name,
+ _py_builtins.get_label_repo_runfiles_path(ctx.label),
+ )
+ result = []
+ for import_str in ctx.attr.imports:
+ import_str = ctx.expand_make_variables("imports", import_str, {})
+ if import_str.startswith("/"):
+ continue
+
+ # To prevent "escaping" out of the runfiles tree, we normalize
+ # the path and ensure it doesn't have up-level references.
+ import_path = paths.normalize("{}/{}".format(prefix, import_str))
+ if import_path.startswith("../") or import_path == "..":
+ fail("Path '{}' references a path above the execution root".format(
+ import_str,
+ ))
+ result.append(import_path)
+ return result
+
+def convert_legacy_create_init_to_int(kwargs):
+ """Convert "legacy_create_init" key to int, in-place.
+
+ Args:
+ kwargs: The kwargs to modify. The key "legacy_create_init", if present
+ and bool, will be converted to its integer value, in place.
+ """
+ if is_bool(kwargs.get("legacy_create_init")):
+ kwargs["legacy_create_init"] = 1 if kwargs["legacy_create_init"] else 0
diff --git a/python/private/common/providers.bzl b/python/private/common/providers.bzl
new file mode 100644
index 0000000000..8a5089d976
--- /dev/null
+++ b/python/private/common/providers.bzl
@@ -0,0 +1,224 @@
+# 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.
+"""Providers for Python rules."""
+
+load("@rules_python_internal//:rules_python_config.bzl", "config")
+load(":semantics.bzl", "TOOLS_REPO")
+
+# TODO: load CcInfo from rules_cc
+_CcInfo = CcInfo
+
+DEFAULT_STUB_SHEBANG = "#!/usr/bin/env python3"
+
+DEFAULT_BOOTSTRAP_TEMPLATE = "@" + TOOLS_REPO + "//tools/python:python_bootstrap_template.txt"
+_PYTHON_VERSION_VALUES = ["PY2", "PY3"]
+
+# Helper to make the provider definitions not crash under Bazel 5.4:
+# Bazel 5.4 doesn't support the `init` arg of `provider()`, so we have to
+# not pass that when using Bazel 5.4. But, not passing the `init` arg
+# changes the return value from a two-tuple to a single value, which then
+# breaks Bazel 6+ code.
+# This isn't actually used under Bazel 5.4, so just stub out the values
+# to get past the loading phase.
+def _define_provider(doc, fields, **kwargs):
+ if not config.enable_pystar:
+ return provider("Stub, not used", fields = []), None
+ return provider(doc = doc, fields = fields, **kwargs)
+
+def _PyRuntimeInfo_init(
+ *,
+ interpreter_path = None,
+ interpreter = None,
+ files = None,
+ coverage_tool = None,
+ coverage_files = None,
+ python_version,
+ stub_shebang = None,
+ bootstrap_template = None):
+ if (interpreter_path and interpreter) or (not interpreter_path and not interpreter):
+ fail("exactly one of interpreter or interpreter_path must be specified")
+
+ if interpreter_path and files != None:
+ fail("cannot specify 'files' if 'interpreter_path' is given")
+
+ if (coverage_tool and not coverage_files) or (not coverage_tool and coverage_files):
+ fail(
+ "coverage_tool and coverage_files must both be set or neither must be set, " +
+ "got coverage_tool={}, coverage_files={}".format(
+ coverage_tool,
+ coverage_files,
+ ),
+ )
+
+ if python_version not in _PYTHON_VERSION_VALUES:
+ fail("invalid python_version: '{}'; must be one of {}".format(
+ python_version,
+ _PYTHON_VERSION_VALUES,
+ ))
+
+ if files != None and type(files) != type(depset()):
+ fail("invalid files: got value of type {}, want depset".format(type(files)))
+
+ if interpreter:
+ if files == None:
+ files = depset()
+ else:
+ files = None
+
+ if coverage_files == None:
+ coverage_files = depset()
+
+ if not stub_shebang:
+ stub_shebang = DEFAULT_STUB_SHEBANG
+
+ return {
+ "bootstrap_template": bootstrap_template,
+ "coverage_files": coverage_files,
+ "coverage_tool": coverage_tool,
+ "files": files,
+ "interpreter": interpreter,
+ "interpreter_path": interpreter_path,
+ "python_version": python_version,
+ "stub_shebang": stub_shebang,
+ }
+
+# TODO(#15897): Rename this to PyRuntimeInfo when we're ready to replace the Java
+# implemented provider with the Starlark one.
+PyRuntimeInfo, _unused_raw_py_runtime_info_ctor = _define_provider(
+ doc = """Contains information about a Python runtime, as returned by the `py_runtime`
+rule.
+
+A Python runtime 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 `File` 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.
+""",
+ init = _PyRuntimeInfo_init,
+ fields = {
+ "bootstrap_template": (
+ "See py_runtime_rule.bzl%py_runtime.bootstrap_template for docs."
+ ),
+ "coverage_files": (
+ "The files required at runtime for using `coverage_tool`. " +
+ "Will be `None` if no `coverage_tool` was provided."
+ ),
+ "coverage_tool": (
+ "If set, this field is a `File` representing tool used for collecting code coverage information from python tests. 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 runtime (in particular, files needed by `interpreter`). " +
+ "The value of `interpreter` need not be included in this field. If " +
+ "this is a platform runtime then this field is `None`."
+ ),
+ "interpreter": (
+ "If this is an in-build runtime, this field is a `File` representing " +
+ "the interpreter. Otherwise, this is `None`. Note that an in-build " +
+ "runtime can use either a prebuilt, checked-in interpreter or an " +
+ "interpreter built from source."
+ ),
+ "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`."
+ ),
+ "python_version": (
+ "Indicates whether this runtime uses Python major version 2 or 3. " +
+ "Valid values are (only) `\"PY2\"` and " +
+ "`\"PY3\"`."
+ ),
+ "stub_shebang": (
+ "\"Shebang\" expression prepended to the bootstrapping Python stub " +
+ "script used when executing `py_binary` targets. Does not " +
+ "apply to Windows."
+ ),
+ },
+)
+
+def _check_arg_type(name, required_type, value):
+ value_type = type(value)
+ if value_type != required_type:
+ fail("parameter '{}' got value of type '{}', want '{}'".format(
+ name,
+ value_type,
+ required_type,
+ ))
+
+def _PyInfo_init(
+ *,
+ transitive_sources,
+ uses_shared_libraries = False,
+ imports = depset(),
+ has_py2_only_sources = False,
+ has_py3_only_sources = False):
+ _check_arg_type("transitive_sources", "depset", transitive_sources)
+
+ # Verify it's postorder compatible, but retain is original ordering.
+ depset(transitive = [transitive_sources], order = "postorder")
+
+ _check_arg_type("uses_shared_libraries", "bool", uses_shared_libraries)
+ _check_arg_type("imports", "depset", imports)
+ _check_arg_type("has_py2_only_sources", "bool", has_py2_only_sources)
+ _check_arg_type("has_py3_only_sources", "bool", has_py3_only_sources)
+ return {
+ "has_py2_only_sources": has_py2_only_sources,
+ "has_py3_only_sources": has_py2_only_sources,
+ "imports": imports,
+ "transitive_sources": transitive_sources,
+ "uses_shared_libraries": uses_shared_libraries,
+ }
+
+PyInfo, _unused_raw_py_info_ctor = _define_provider(
+ doc = "Encapsulates information provided by the Python rules.",
+ init = _PyInfo_init,
+ fields = {
+ "has_py2_only_sources": "Whether any of this target's transitive sources requires a Python 2 runtime.",
+ "has_py3_only_sources": "Whether any of this target's transitive sources requires a Python 3 runtime.",
+ "imports": """\
+A depset of import path strings to be added to the `PYTHONPATH` of executable
+Python targets. These are accumulated from the transitive `deps`.
+The order of the depset is not guaranteed and may be changed in the future. It
+is recommended to use `default` order (the default).
+""",
+ "transitive_sources": """\
+A (`postorder`-compatible) depset of `.py` files appearing in the target's
+`srcs` and the `srcs` of the target's transitive `deps`.
+""",
+ "uses_shared_libraries": """
+Whether any of this target's transitive `deps` has a shared library file (such
+as a `.so` file).
+
+This field is currently unused in Bazel and may go away in the future.
+""",
+ },
+)
+
+def _PyCcLinkParamsProvider_init(cc_info):
+ return {
+ "cc_info": _CcInfo(linking_context = cc_info.linking_context),
+ }
+
+# buildifier: disable=name-conventions
+PyCcLinkParamsProvider, _unused_raw_py_cc_link_params_provider_ctor = _define_provider(
+ doc = ("Python-wrapper to forward CcInfo.linking_context. This is to " +
+ "allow Python targets to propagate C++ linking information, but " +
+ "without the Python target appearing to be a valid C++ rule dependency"),
+ init = _PyCcLinkParamsProvider_init,
+ fields = {
+ "cc_info": "A CcInfo instance; it has only linking_context set",
+ },
+)
diff --git a/python/private/common/py_binary_macro_bazel.bzl b/python/private/common/py_binary_macro_bazel.bzl
new file mode 100644
index 0000000000..a6c4e97dac
--- /dev/null
+++ b/python/private/common/py_binary_macro_bazel.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.
+"""Implementation of macro-half of py_binary rule."""
+
+load(":common_bazel.bzl", "convert_legacy_create_init_to_int")
+load(":py_binary_rule_bazel.bzl", py_binary_rule = "py_binary")
+
+def py_binary(**kwargs):
+ convert_legacy_create_init_to_int(kwargs)
+ py_binary_rule(**kwargs)
diff --git a/python/private/common/py_binary_rule_bazel.bzl b/python/private/common/py_binary_rule_bazel.bzl
new file mode 100644
index 0000000000..491d9050da
--- /dev/null
+++ b/python/private/common/py_binary_rule_bazel.bzl
@@ -0,0 +1,49 @@
+# 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.
+"""Rule implementation of py_binary for Bazel."""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load(":attributes.bzl", "AGNOSTIC_BINARY_ATTRS")
+load(
+ ":py_executable_bazel.bzl",
+ "create_executable_rule",
+ "py_executable_bazel_impl",
+)
+load(":semantics.bzl", "TOOLS_REPO")
+
+_PY_TEST_ATTRS = {
+ "_collect_cc_coverage": attr.label(
+ default = "@" + TOOLS_REPO + "//tools/test:collect_cc_coverage",
+ executable = True,
+ cfg = "exec",
+ ),
+ "_lcov_merger": attr.label(
+ default = configuration_field(fragment = "coverage", name = "output_generator"),
+ executable = True,
+ cfg = "exec",
+ ),
+}
+
+def _py_binary_impl(ctx):
+ return py_executable_bazel_impl(
+ ctx = ctx,
+ is_test = False,
+ inherited_environment = [],
+ )
+
+py_binary = create_executable_rule(
+ implementation = _py_binary_impl,
+ attrs = dicts.add(AGNOSTIC_BINARY_ATTRS, _PY_TEST_ATTRS),
+ executable = True,
+)
diff --git a/python/private/common/py_executable.bzl b/python/private/common/py_executable.bzl
new file mode 100644
index 0000000000..bb1f16d61a
--- /dev/null
+++ b/python/private/common/py_executable.bzl
@@ -0,0 +1,861 @@
+# 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.
+"""Common functionality between test/binary executables."""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load(
+ ":attributes.bzl",
+ "AGNOSTIC_EXECUTABLE_ATTRS",
+ "COMMON_ATTRS",
+ "PY_SRCS_ATTRS",
+ "SRCS_VERSION_ALL_VALUES",
+ "create_srcs_attr",
+ "create_srcs_version_attr",
+)
+load(":cc_helper.bzl", "cc_helper")
+load(
+ ":common.bzl",
+ "TOOLCHAIN_TYPE",
+ "check_native_allowed",
+ "collect_imports",
+ "collect_runfiles",
+ "create_instrumented_files_info",
+ "create_output_group_info",
+ "create_py_info",
+ "csv",
+ "filter_to_py_srcs",
+ "target_platform_has_any_constraint",
+ "union_attrs",
+)
+load(
+ ":providers.bzl",
+ "PyCcLinkParamsProvider",
+ "PyRuntimeInfo",
+)
+load(":py_internal.bzl", "py_internal")
+load(
+ ":semantics.bzl",
+ "ALLOWED_MAIN_EXTENSIONS",
+ "BUILD_DATA_SYMLINK_PATH",
+ "IS_BAZEL",
+ "PLATFORMS_LOCATION",
+ "PY_RUNTIME_ATTR_NAME",
+ "TOOLS_REPO",
+)
+
+# TODO: Load cc_common from rules_cc
+_cc_common = cc_common
+
+_py_builtins = py_internal
+
+# Bazel 5.4 doesn't have config_common.toolchain_type
+_CC_TOOLCHAINS = [config_common.toolchain_type(
+ "@" + TOOLS_REPO + "//tools/cpp:toolchain_type",
+ mandatory = False,
+)] if hasattr(config_common, "toolchain_type") else []
+
+# Non-Google-specific attributes for executables
+# These attributes are for rules that accept Python sources.
+EXECUTABLE_ATTRS = union_attrs(
+ COMMON_ATTRS,
+ AGNOSTIC_EXECUTABLE_ATTRS,
+ PY_SRCS_ATTRS,
+ {
+ # TODO(b/203567235): In the Java impl, any file is allowed. While marked
+ # label, it is more treated as a string, and doesn't have to refer to
+ # anything that exists because it gets treated as suffix-search string
+ # over `srcs`.
+ "main": attr.label(
+ allow_single_file = True,
+ doc = """\
+Optional; the name of the source file that is the main entry point of the
+application. This file must also be listed in `srcs`. If left unspecified,
+`name`, with `.py` appended, is used instead. If `name` does not match any
+filename in `srcs`, `main` must be specified.
+""",
+ ),
+ # TODO(b/203567235): In Google, this attribute is deprecated, and can
+ # only effectively be PY3. Externally, with Bazel, this attribute has
+ # a separate story.
+ "python_version": attr.string(
+ # TODO(b/203567235): In the Java impl, the default comes from
+ # --python_version. Not clear what the Starlark equivalent is.
+ default = "PY3",
+ # NOTE: Some tests care about the order of these values.
+ values = ["PY2", "PY3"],
+ ),
+ "_windows_constraints": attr.label_list(
+ default = [
+ PLATFORMS_LOCATION + "/os:windows",
+ ],
+ ),
+ },
+ create_srcs_version_attr(values = SRCS_VERSION_ALL_VALUES),
+ create_srcs_attr(mandatory = True),
+ allow_none = True,
+)
+
+def py_executable_base_impl(ctx, *, semantics, is_test, inherited_environment = []):
+ """Base rule implementation for a Python executable.
+
+ Google and Bazel call this common base and apply customizations using the
+ semantics object.
+
+ Args:
+ ctx: The rule ctx
+ semantics: BinarySemantics struct; see create_binary_semantics_struct()
+ is_test: bool, True if the rule is a test rule (has `test=True`),
+ False if not (has `executable=True`)
+ inherited_environment: List of str; additional environment variable
+ names that should be inherited from the runtime environment when the
+ executable is run.
+ Returns:
+ DefaultInfo provider for the executable
+ """
+ _validate_executable(ctx)
+
+ main_py = determine_main(ctx)
+ direct_sources = filter_to_py_srcs(ctx.files.srcs)
+ output_sources = semantics.maybe_precompile(ctx, direct_sources)
+ imports = collect_imports(ctx, semantics)
+ executable, files_to_build = _compute_outputs(ctx, output_sources)
+
+ runtime_details = _get_runtime_details(ctx, semantics)
+ if ctx.configuration.coverage_enabled:
+ extra_deps = semantics.get_coverage_deps(ctx, runtime_details)
+ else:
+ extra_deps = []
+
+ # The debugger dependency should be prevented by select() config elsewhere,
+ # but just to be safe, also guard against adding it to the output here.
+ if not _is_tool_config(ctx):
+ extra_deps.extend(semantics.get_debugger_deps(ctx, runtime_details))
+
+ cc_details = semantics.get_cc_details_for_binary(ctx, extra_deps = extra_deps)
+ native_deps_details = _get_native_deps_details(
+ ctx,
+ semantics = semantics,
+ cc_details = cc_details,
+ is_test = is_test,
+ )
+ runfiles_details = _get_base_runfiles_for_binary(
+ ctx,
+ executable = executable,
+ extra_deps = extra_deps,
+ files_to_build = files_to_build,
+ extra_common_runfiles = [
+ runtime_details.runfiles,
+ cc_details.extra_runfiles,
+ native_deps_details.runfiles,
+ semantics.get_extra_common_runfiles_for_binary(ctx),
+ ],
+ semantics = semantics,
+ )
+ exec_result = semantics.create_executable(
+ ctx,
+ executable = executable,
+ main_py = main_py,
+ imports = imports,
+ is_test = is_test,
+ runtime_details = runtime_details,
+ cc_details = cc_details,
+ native_deps_details = native_deps_details,
+ runfiles_details = runfiles_details,
+ )
+ files_to_build = depset(transitive = [
+ exec_result.extra_files_to_build,
+ files_to_build,
+ ])
+ extra_exec_runfiles = ctx.runfiles(transitive_files = files_to_build)
+ runfiles_details = struct(
+ default_runfiles = runfiles_details.default_runfiles.merge(extra_exec_runfiles),
+ data_runfiles = runfiles_details.data_runfiles.merge(extra_exec_runfiles),
+ )
+
+ legacy_providers, modern_providers = _create_providers(
+ ctx = ctx,
+ executable = executable,
+ runfiles_details = runfiles_details,
+ main_py = main_py,
+ imports = imports,
+ direct_sources = direct_sources,
+ files_to_build = files_to_build,
+ runtime_details = runtime_details,
+ cc_info = cc_details.cc_info_for_propagating,
+ inherited_environment = inherited_environment,
+ semantics = semantics,
+ output_groups = exec_result.output_groups,
+ )
+ return struct(
+ legacy_providers = legacy_providers,
+ providers = modern_providers,
+ )
+
+def _validate_executable(ctx):
+ if ctx.attr.python_version != "PY3":
+ fail("It is not allowed to use Python 2")
+ check_native_allowed(ctx)
+
+def _compute_outputs(ctx, output_sources):
+ if target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints):
+ executable = ctx.actions.declare_file(ctx.label.name + ".exe")
+ else:
+ executable = ctx.actions.declare_file(ctx.label.name)
+
+ # TODO(b/208657718): Remove output_sources from the default outputs
+ # once the depot is cleaned up.
+ return executable, depset([executable] + output_sources)
+
+def _get_runtime_details(ctx, semantics):
+ """Gets various information about the Python runtime to use.
+
+ While most information comes from the toolchain, various legacy and
+ compatibility behaviors require computing some other information.
+
+ Args:
+ ctx: Rule ctx
+ semantics: A `BinarySemantics` struct; see `create_binary_semantics_struct`
+
+ Returns:
+ A struct; see inline-field comments of the return value for details.
+ """
+
+ # Bazel has --python_path. This flag has a computed default of "python" when
+ # its actual default is null (see
+ # BazelPythonConfiguration.java#getPythonPath). This flag is only used if
+ # toolchains are not enabled and `--python_top` isn't set. Note that Google
+ # used to have a variant of this named --python_binary, but it has since
+ # been removed.
+ #
+ # TOOD(bazelbuild/bazel#7901): Remove this once --python_path flag is removed.
+
+ if IS_BAZEL:
+ flag_interpreter_path = ctx.fragments.bazel_py.python_path
+ toolchain_runtime, effective_runtime = _maybe_get_runtime_from_ctx(ctx)
+ if not effective_runtime:
+ # Clear these just in case
+ toolchain_runtime = None
+ effective_runtime = None
+
+ else: # Google code path
+ flag_interpreter_path = None
+ toolchain_runtime, effective_runtime = _maybe_get_runtime_from_ctx(ctx)
+ if not effective_runtime:
+ fail("Unable to find Python runtime")
+
+ if effective_runtime:
+ direct = [] # List of files
+ transitive = [] # List of depsets
+ if effective_runtime.interpreter:
+ direct.append(effective_runtime.interpreter)
+ transitive.append(effective_runtime.files)
+
+ if ctx.configuration.coverage_enabled:
+ if effective_runtime.coverage_tool:
+ direct.append(effective_runtime.coverage_tool)
+ if effective_runtime.coverage_files:
+ transitive.append(effective_runtime.coverage_files)
+ runtime_files = depset(direct = direct, transitive = transitive)
+ else:
+ runtime_files = depset()
+
+ executable_interpreter_path = semantics.get_interpreter_path(
+ ctx,
+ runtime = effective_runtime,
+ flag_interpreter_path = flag_interpreter_path,
+ )
+
+ return struct(
+ # Optional PyRuntimeInfo: The runtime found from toolchain resolution.
+ # This may be None because, within Google, toolchain resolution isn't
+ # yet enabled.
+ toolchain_runtime = toolchain_runtime,
+ # Optional PyRuntimeInfo: The runtime that should be used. When
+ # toolchain resolution is enabled, this is the same as
+ # `toolchain_resolution`. Otherwise, this probably came from the
+ # `_python_top` attribute that the Google implementation still uses.
+ # This is separate from `toolchain_runtime` because toolchain_runtime
+ # is propagated as a provider, while non-toolchain runtimes are not.
+ effective_runtime = effective_runtime,
+ # str; Path to the Python interpreter to use for running the executable
+ # itself (not the bootstrap script). Either an absolute path (which
+ # means it is platform-specific), or a runfiles-relative path (which
+ # means the interpreter should be within `runtime_files`)
+ executable_interpreter_path = executable_interpreter_path,
+ # runfiles: Additional runfiles specific to the runtime that should
+ # be included. For in-build runtimes, this shold include the interpreter
+ # and any supporting files.
+ runfiles = ctx.runfiles(transitive_files = runtime_files),
+ )
+
+def _maybe_get_runtime_from_ctx(ctx):
+ """Finds the PyRuntimeInfo from the toolchain or attribute, if available.
+
+ Returns:
+ 2-tuple of toolchain_runtime, effective_runtime
+ """
+ if ctx.fragments.py.use_toolchains:
+ toolchain = ctx.toolchains[TOOLCHAIN_TYPE]
+
+ if not hasattr(toolchain, "py3_runtime"):
+ fail("Python toolchain field 'py3_runtime' is missing")
+ if not toolchain.py3_runtime:
+ fail("Python toolchain missing py3_runtime")
+ py3_runtime = toolchain.py3_runtime
+
+ # Hack around the fact that the autodetecting Python toolchain, which is
+ # automatically registered, does not yet support Windows. In this case,
+ # we want to return null so that _get_interpreter_path falls back on
+ # --python_path. See tools/python/toolchain.bzl.
+ # TODO(#7844): Remove this hack when the autodetecting toolchain has a
+ # Windows implementation.
+ if py3_runtime.interpreter_path == "/_magic_pyruntime_sentinel_do_not_use":
+ return None, None
+
+ if py3_runtime.python_version != "PY3":
+ fail("Python toolchain py3_runtime must be python_version=PY3, got {}".format(
+ py3_runtime.python_version,
+ ))
+ toolchain_runtime = toolchain.py3_runtime
+ effective_runtime = toolchain_runtime
+ else:
+ toolchain_runtime = None
+ attr_target = getattr(ctx.attr, PY_RUNTIME_ATTR_NAME)
+
+ # In Bazel, --python_top is null by default.
+ if attr_target and PyRuntimeInfo in attr_target:
+ effective_runtime = attr_target[PyRuntimeInfo]
+ else:
+ return None, None
+
+ return toolchain_runtime, effective_runtime
+
+def _get_base_runfiles_for_binary(
+ ctx,
+ *,
+ executable,
+ extra_deps,
+ files_to_build,
+ extra_common_runfiles,
+ semantics):
+ """Returns the set of runfiles necessary prior to executable creation.
+
+ NOTE: The term "common runfiles" refers to the runfiles that both the
+ default and data runfiles have in common.
+
+ Args:
+ ctx: The rule ctx.
+ executable: The main executable output.
+ extra_deps: List of Targets; additional targets whose runfiles
+ will be added to the common runfiles.
+ files_to_build: depset of File of the default outputs to add into runfiles.
+ extra_common_runfiles: List of runfiles; additional runfiles that
+ will be added to the common runfiles.
+ semantics: A `BinarySemantics` struct; see `create_binary_semantics_struct`.
+
+ Returns:
+ struct with attributes:
+ * default_runfiles: The default runfiles
+ * data_runfiles: The data runfiles
+ """
+ common_runfiles = collect_runfiles(ctx, depset(
+ direct = [executable],
+ transitive = [files_to_build],
+ ))
+ if extra_deps:
+ common_runfiles = common_runfiles.merge_all([
+ t[DefaultInfo].default_runfiles
+ for t in extra_deps
+ ])
+ common_runfiles = common_runfiles.merge_all(extra_common_runfiles)
+
+ if semantics.should_create_init_files(ctx):
+ common_runfiles = _py_builtins.merge_runfiles_with_generated_inits_empty_files_supplier(
+ ctx = ctx,
+ runfiles = common_runfiles,
+ )
+
+ # Don't include build_data.txt in data runfiles. This allows binaries to
+ # contain other binaries while still using the same fixed location symlink
+ # for the build_data.txt file. Really, the fixed location symlink should be
+ # removed and another way found to locate the underlying build data file.
+ data_runfiles = common_runfiles
+
+ if is_stamping_enabled(ctx, semantics) and semantics.should_include_build_data(ctx):
+ default_runfiles = common_runfiles.merge(_create_runfiles_with_build_data(
+ ctx,
+ semantics.get_central_uncachable_version_file(ctx),
+ semantics.get_extra_write_build_data_env(ctx),
+ ))
+ else:
+ default_runfiles = common_runfiles
+
+ return struct(
+ default_runfiles = default_runfiles,
+ data_runfiles = data_runfiles,
+ )
+
+def _create_runfiles_with_build_data(
+ ctx,
+ central_uncachable_version_file,
+ extra_write_build_data_env):
+ return ctx.runfiles(
+ symlinks = {
+ BUILD_DATA_SYMLINK_PATH: _write_build_data(
+ ctx,
+ central_uncachable_version_file,
+ extra_write_build_data_env,
+ ),
+ },
+ )
+
+def _write_build_data(ctx, central_uncachable_version_file, extra_write_build_data_env):
+ # TODO: Remove this logic when a central file is always available
+ if not central_uncachable_version_file:
+ version_file = ctx.actions.declare_file(ctx.label.name + "-uncachable_version_file.txt")
+ _py_builtins.copy_without_caching(
+ ctx = ctx,
+ read_from = ctx.version_file,
+ write_to = version_file,
+ )
+ else:
+ version_file = central_uncachable_version_file
+
+ direct_inputs = [ctx.info_file, version_file]
+
+ # A "constant metadata" file is basically a special file that doesn't
+ # support change detection logic and reports that it is unchanged. i.e., it
+ # behaves like ctx.version_file and is ignored when computing "what inputs
+ # changed" (see https://bazel.build/docs/user-manual#workspace-status).
+ #
+ # We do this so that consumers of the final build data file don't have
+ # to transitively rebuild everything -- the `uncachable_version_file` file
+ # isn't cachable, which causes the build data action to always re-run.
+ #
+ # While this technically means a binary could have stale build info,
+ # it ends up not mattering in practice because the volatile information
+ # doesn't meaningfully effect other outputs.
+ #
+ # This is also done for performance and Make It work reasons:
+ # * Passing the transitive dependencies into the action requires passing
+ # the runfiles, but actions don't directly accept runfiles. While
+ # flattening the depsets can be deferred, accessing the
+ # `runfiles.empty_filenames` attribute will will invoke the empty
+ # file supplier a second time, which is too much of a memory and CPU
+ # performance hit.
+ # * Some targets specify a directory in `data`, which is unsound, but
+ # mostly works. Google's RBE, unfortunately, rejects it.
+ # * A binary's transitive closure may be so large that it exceeds
+ # Google RBE limits for action inputs.
+ build_data = _py_builtins.declare_constant_metadata_file(
+ ctx = ctx,
+ name = ctx.label.name + ".build_data.txt",
+ root = ctx.bin_dir,
+ )
+
+ ctx.actions.run(
+ executable = ctx.executable._build_data_gen,
+ env = dicts.add({
+ # NOTE: ctx.info_file is undocumented; see
+ # https://github.com/bazelbuild/bazel/issues/9363
+ "INFO_FILE": ctx.info_file.path,
+ "OUTPUT": build_data.path,
+ "PLATFORM": cc_helper.find_cpp_toolchain(ctx).toolchain_id,
+ "TARGET": str(ctx.label),
+ "VERSION_FILE": version_file.path,
+ }, extra_write_build_data_env),
+ inputs = depset(
+ direct = direct_inputs,
+ ),
+ outputs = [build_data],
+ mnemonic = "PyWriteBuildData",
+ progress_message = "Generating %{label} build_data.txt",
+ )
+ return build_data
+
+def _get_native_deps_details(ctx, *, semantics, cc_details, is_test):
+ if not semantics.should_build_native_deps_dso(ctx):
+ return struct(dso = None, runfiles = ctx.runfiles())
+
+ cc_info = cc_details.cc_info_for_self_link
+
+ if not cc_info.linking_context.linker_inputs:
+ return struct(dso = None, runfiles = ctx.runfiles())
+
+ dso = ctx.actions.declare_file(semantics.get_native_deps_dso_name(ctx))
+ share_native_deps = py_internal.share_native_deps(ctx)
+ cc_feature_config = cc_configure_features(
+ ctx,
+ cc_toolchain = cc_details.cc_toolchain,
+ # See b/171276569#comment18: this feature string is just to allow
+ # Google's RBE to know the link action is for the Python case so it can
+ # take special actions (though as of Jun 2022, no special action is
+ # taken).
+ extra_features = ["native_deps_link"],
+ )
+ if share_native_deps:
+ linked_lib = _create_shared_native_deps_dso(
+ ctx,
+ cc_info = cc_info,
+ is_test = is_test,
+ requested_features = cc_feature_config.requested_features,
+ feature_configuration = cc_feature_config.feature_configuration,
+ )
+ ctx.actions.symlink(
+ output = dso,
+ target_file = linked_lib,
+ progress_message = "Symlinking shared native deps for %{label}",
+ )
+ else:
+ linked_lib = dso
+ _cc_common.link(
+ name = ctx.label.name,
+ actions = ctx.actions,
+ linking_contexts = [cc_info.linking_context],
+ output_type = "dynamic_library",
+ never_link = True,
+ native_deps = True,
+ feature_configuration = cc_feature_config.feature_configuration,
+ cc_toolchain = cc_details.cc_toolchain,
+ test_only_target = is_test,
+ stamp = 1 if is_stamping_enabled(ctx, semantics) else 0,
+ main_output = linked_lib,
+ use_shareable_artifact_factory = True,
+ # NOTE: Only flags not captured by cc_info.linking_context need to
+ # be manually passed
+ user_link_flags = semantics.get_native_deps_user_link_flags(ctx),
+ )
+ return struct(
+ dso = dso,
+ runfiles = ctx.runfiles(files = [dso]),
+ )
+
+def _create_shared_native_deps_dso(
+ ctx,
+ *,
+ cc_info,
+ is_test,
+ feature_configuration,
+ requested_features):
+ linkstamps = cc_info.linking_context.linkstamps()
+
+ partially_disabled_thin_lto = (
+ _cc_common.is_enabled(
+ feature_name = "thin_lto_linkstatic_tests_use_shared_nonlto_backends",
+ feature_configuration = feature_configuration,
+ ) and not _cc_common.is_enabled(
+ feature_name = "thin_lto_all_linkstatic_use_shared_nonlto_backends",
+ feature_configuration = feature_configuration,
+ )
+ )
+ dso_hash = _get_shared_native_deps_hash(
+ linker_inputs = cc_helper.get_static_mode_params_for_dynamic_library_libraries(
+ depset([
+ lib
+ for linker_input in cc_info.linking_context.linker_inputs.to_list()
+ for lib in linker_input.libraries
+ ]),
+ ),
+ link_opts = [
+ flag
+ for input in cc_info.linking_context.linker_inputs.to_list()
+ for flag in input.user_link_flags
+ ],
+ linkstamps = [linkstamp.file() for linkstamp in linkstamps.to_list()],
+ build_info_artifacts = _cc_common.get_build_info(ctx) if linkstamps else [],
+ features = requested_features,
+ is_test_target_partially_disabled_thin_lto = is_test and partially_disabled_thin_lto,
+ )
+ return py_internal.declare_shareable_artifact(ctx, "_nativedeps/%x.so" % dso_hash)
+
+# This is a minimal version of NativeDepsHelper.getSharedNativeDepsPath, see
+# com.google.devtools.build.lib.rules.nativedeps.NativeDepsHelper#getSharedNativeDepsPath
+# The basic idea is to take all the inputs that affect linking and encode (via
+# hashing) them into the filename.
+# TODO(b/234232820): The settings that affect linking must be kept in sync with the actual
+# C++ link action. For more information, see the large descriptive comment on
+# NativeDepsHelper#getSharedNativeDepsPath.
+def _get_shared_native_deps_hash(
+ *,
+ linker_inputs,
+ link_opts,
+ linkstamps,
+ build_info_artifacts,
+ features,
+ is_test_target_partially_disabled_thin_lto):
+ # NOTE: We use short_path because the build configuration root in which
+ # files are always created already captures the configuration-specific
+ # parts, so no need to include them manually.
+ parts = []
+ for artifact in linker_inputs:
+ parts.append(artifact.short_path)
+ parts.append(str(len(link_opts)))
+ parts.extend(link_opts)
+ for artifact in linkstamps:
+ parts.append(artifact.short_path)
+ for artifact in build_info_artifacts:
+ parts.append(artifact.short_path)
+ parts.extend(sorted(features))
+
+ # Sharing of native dependencies may cause an {@link
+ # ActionConflictException} when ThinLTO is disabled for test and test-only
+ # targets that are statically linked, but enabled for other statically
+ # linked targets. This happens in case the artifacts for the shared native
+ # dependency are output by {@link Action}s owned by the non-test and test
+ # targets both. To fix this, we allow creation of multiple artifacts for the
+ # shared native library - one shared among the test and test-only targets
+ # where ThinLTO is disabled, and the other shared among other targets where
+ # ThinLTO is enabled. See b/138118275
+ parts.append("1" if is_test_target_partially_disabled_thin_lto else "0")
+
+ return hash("".join(parts))
+
+def determine_main(ctx):
+ """Determine the main entry point .py source file.
+
+ Args:
+ ctx: The rule ctx.
+
+ Returns:
+ Artifact; the main file. If one can't be found, an error is raised.
+ """
+ if ctx.attr.main:
+ proposed_main = ctx.attr.main.label.name
+ if not proposed_main.endswith(tuple(ALLOWED_MAIN_EXTENSIONS)):
+ fail("main must end in '.py'")
+ else:
+ if ctx.label.name.endswith(".py"):
+ fail("name must not end in '.py'")
+ proposed_main = ctx.label.name + ".py"
+
+ main_files = [src for src in ctx.files.srcs if _path_endswith(src.short_path, proposed_main)]
+ if not main_files:
+ if ctx.attr.main:
+ fail("could not find '{}' as specified by 'main' attribute".format(proposed_main))
+ else:
+ fail(("corresponding default '{}' does not appear in srcs. Add " +
+ "it or override default file name with a 'main' attribute").format(
+ proposed_main,
+ ))
+
+ elif len(main_files) > 1:
+ if ctx.attr.main:
+ fail(("file name '{}' specified by 'main' attributes matches multiple files. " +
+ "Matches: {}").format(
+ proposed_main,
+ csv([f.short_path for f in main_files]),
+ ))
+ else:
+ fail(("default main file '{}' matches multiple files in srcs. Perhaps specify " +
+ "an explicit file with 'main' attribute? Matches were: {}").format(
+ proposed_main,
+ csv([f.short_path for f in main_files]),
+ ))
+ return main_files[0]
+
+def _path_endswith(path, endswith):
+ # Use slash to anchor each path to prevent e.g.
+ # "ab/c.py".endswith("b/c.py") from incorrectly matching.
+ return ("/" + path).endswith("/" + endswith)
+
+def is_stamping_enabled(ctx, semantics):
+ """Tells if stamping is enabled or not.
+
+ Args:
+ ctx: The rule ctx
+ semantics: a semantics struct (see create_semantics_struct).
+ Returns:
+ bool; True if stamping is enabled, False if not.
+ """
+ if _is_tool_config(ctx):
+ return False
+
+ stamp = ctx.attr.stamp
+ if stamp == 1:
+ return True
+ elif stamp == 0:
+ return False
+ elif stamp == -1:
+ return semantics.get_stamp_flag(ctx)
+ else:
+ fail("Unsupported `stamp` value: {}".format(stamp))
+
+def _is_tool_config(ctx):
+ # NOTE: The is_tool_configuration() function is only usable by builtins.
+ # See https://github.com/bazelbuild/bazel/issues/14444 for the FR for
+ # a more public API. Until that's available, py_internal to the rescue.
+ return py_internal.is_tool_configuration(ctx)
+
+def _create_providers(
+ *,
+ ctx,
+ executable,
+ main_py,
+ direct_sources,
+ files_to_build,
+ runfiles_details,
+ imports,
+ cc_info,
+ inherited_environment,
+ runtime_details,
+ output_groups,
+ semantics):
+ """Creates the providers an executable should return.
+
+ Args:
+ ctx: The rule ctx.
+ executable: File; the target's executable file.
+ main_py: File; the main .py entry point.
+ direct_sources: list of Files; the direct, raw `.py` sources for the target.
+ This should only be Python source files. It should not include pyc
+ files.
+ files_to_build: depset of Files; the files for DefaultInfo.files
+ runfiles_details: runfiles that will become the default and data runfiles.
+ imports: depset of strings; the import paths to propagate
+ cc_info: optional CcInfo; Linking information to propagate as
+ PyCcLinkParamsProvider. Note that only the linking information
+ is propagated, not the whole CcInfo.
+ inherited_environment: list of strings; Environment variable names
+ that should be inherited from the environment the executuble
+ is run within.
+ runtime_details: struct of runtime information; see _get_runtime_details()
+ output_groups: dict[str, depset[File]]; used to create OutputGroupInfo
+ semantics: BinarySemantics struct; see create_binary_semantics()
+
+ Returns:
+ A two-tuple of:
+ 1. A dict of legacy providers.
+ 2. A list of modern providers.
+ """
+ providers = [
+ DefaultInfo(
+ executable = executable,
+ files = files_to_build,
+ default_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles(
+ ctx,
+ runfiles_details.default_runfiles,
+ ),
+ data_runfiles = _py_builtins.make_runfiles_respect_legacy_external_runfiles(
+ ctx,
+ runfiles_details.data_runfiles,
+ ),
+ ),
+ create_instrumented_files_info(ctx),
+ _create_run_environment_info(ctx, inherited_environment),
+ ]
+
+ # TODO(b/265840007): Make this non-conditional once Google enables
+ # --incompatible_use_python_toolchains.
+ if runtime_details.toolchain_runtime:
+ providers.append(runtime_details.toolchain_runtime)
+
+ # TODO(b/163083591): Remove the PyCcLinkParamsProvider once binaries-in-deps
+ # are cleaned up.
+ if cc_info:
+ providers.append(
+ PyCcLinkParamsProvider(cc_info = cc_info),
+ )
+
+ py_info, deps_transitive_sources = create_py_info(
+ ctx,
+ direct_sources = depset(direct_sources),
+ imports = imports,
+ )
+
+ # TODO(b/253059598): Remove support for extra actions; https://github.com/bazelbuild/bazel/issues/16455
+ listeners_enabled = _py_builtins.are_action_listeners_enabled(ctx)
+ if listeners_enabled:
+ _py_builtins.add_py_extra_pseudo_action(
+ ctx = ctx,
+ dependency_transitive_python_sources = deps_transitive_sources,
+ )
+
+ providers.append(py_info)
+ providers.append(create_output_group_info(py_info.transitive_sources, output_groups))
+
+ extra_legacy_providers, extra_providers = semantics.get_extra_providers(
+ ctx,
+ main_py = main_py,
+ runtime_details = runtime_details,
+ )
+ providers.extend(extra_providers)
+ return extra_legacy_providers, providers
+
+def _create_run_environment_info(ctx, inherited_environment):
+ expanded_env = {}
+ for key, value in ctx.attr.env.items():
+ expanded_env[key] = _py_builtins.expand_location_and_make_variables(
+ ctx = ctx,
+ attribute_name = "env[{}]".format(key),
+ expression = value,
+ targets = ctx.attr.data,
+ )
+ return RunEnvironmentInfo(
+ environment = expanded_env,
+ inherited_environment = inherited_environment,
+ )
+
+def create_base_executable_rule(*, attrs, fragments = [], **kwargs):
+ """Create a function for defining for Python binary/test targets.
+
+ Args:
+ attrs: Rule attributes
+ fragments: List of str; extra config fragments that are required.
+ **kwargs: Additional args to pass onto `rule()`
+
+ Returns:
+ A rule function
+ """
+ if "py" not in fragments:
+ # The list might be frozen, so use concatentation
+ fragments = fragments + ["py"]
+ return rule(
+ # TODO: add ability to remove attrs, i.e. for imports attr
+ attrs = dicts.add(EXECUTABLE_ATTRS, attrs),
+ toolchains = [TOOLCHAIN_TYPE] + _CC_TOOLCHAINS,
+ fragments = fragments,
+ **kwargs
+ )
+
+def cc_configure_features(ctx, *, cc_toolchain, extra_features):
+ """Configure C++ features for Python purposes.
+
+ Args:
+ ctx: Rule ctx
+ cc_toolchain: The CcToolchain the target is using.
+ extra_features: list of strings; additional features to request be
+ enabled.
+
+ Returns:
+ struct of the feature configuration and all requested features.
+ """
+ requested_features = ["static_linking_mode"]
+ requested_features.extend(extra_features)
+ requested_features.extend(ctx.features)
+ if "legacy_whole_archive" not in ctx.disabled_features:
+ requested_features.append("legacy_whole_archive")
+ feature_configuration = _cc_common.configure_features(
+ ctx = ctx,
+ cc_toolchain = cc_toolchain,
+ requested_features = requested_features,
+ unsupported_features = ctx.disabled_features,
+ )
+ return struct(
+ feature_configuration = feature_configuration,
+ requested_features = requested_features,
+ )
+
+only_exposed_for_google_internal_reason = struct(
+ create_runfiles_with_build_data = _create_runfiles_with_build_data,
+)
diff --git a/python/private/common/py_executable_bazel.bzl b/python/private/common/py_executable_bazel.bzl
new file mode 100644
index 0000000000..97712c5e43
--- /dev/null
+++ b/python/private/common/py_executable_bazel.bzl
@@ -0,0 +1,484 @@
+# 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.
+"""Implementation for Bazel Python executable."""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load(":attributes_bazel.bzl", "IMPORTS_ATTRS")
+load(
+ ":common.bzl",
+ "create_binary_semantics_struct",
+ "create_cc_details_struct",
+ "create_executable_result_struct",
+ "target_platform_has_any_constraint",
+ "union_attrs",
+)
+load(":common_bazel.bzl", "collect_cc_info", "get_imports", "maybe_precompile")
+load(":providers.bzl", "DEFAULT_STUB_SHEBANG")
+load(
+ ":py_executable.bzl",
+ "create_base_executable_rule",
+ "py_executable_base_impl",
+)
+load(":py_internal.bzl", "py_internal")
+load(":semantics.bzl", "TOOLS_REPO")
+
+_py_builtins = py_internal
+_EXTERNAL_PATH_PREFIX = "external"
+_ZIP_RUNFILES_DIRECTORY_NAME = "runfiles"
+
+BAZEL_EXECUTABLE_ATTRS = union_attrs(
+ IMPORTS_ATTRS,
+ {
+ "legacy_create_init": attr.int(
+ default = -1,
+ values = [-1, 0, 1],
+ doc = """\
+Whether to implicitly create empty `__init__.py` files in the runfiles tree.
+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. The default, `-1` (auto), means true unless
+`--incompatible_default_to_explicit_init_py` is used. If false, the user is
+responsible for creating (possibly empty) `__init__.py` files and adding them to
+the `srcs` of Python targets as required.
+ """,
+ ),
+ "_bootstrap_template": attr.label(
+ allow_single_file = True,
+ default = "@" + TOOLS_REPO + "//tools/python:python_bootstrap_template.txt",
+ ),
+ "_launcher": attr.label(
+ cfg = "target",
+ default = "@" + TOOLS_REPO + "//tools/launcher:launcher",
+ executable = True,
+ ),
+ "_py_interpreter": attr.label(
+ # The configuration_field args are validated when called;
+ # we use the precense of py_internal to indicate this Bazel
+ # build has that fragment and name.
+ default = configuration_field(
+ fragment = "bazel_py",
+ name = "python_top",
+ ) if py_internal else None,
+ ),
+ # TODO: This appears to be vestigial. It's only added because
+ # GraphlessQueryTest.testLabelsOperator relies on it to test for
+ # query behavior of implicit dependencies.
+ "_py_toolchain_type": attr.label(
+ default = "@" + TOOLS_REPO + "//tools/python:toolchain_type",
+ ),
+ "_windows_launcher_maker": attr.label(
+ default = "@" + TOOLS_REPO + "//tools/launcher:launcher_maker",
+ cfg = "exec",
+ executable = True,
+ ),
+ "_zipper": attr.label(
+ cfg = "exec",
+ executable = True,
+ default = "@" + TOOLS_REPO + "//tools/zip:zipper",
+ ),
+ },
+)
+
+def create_executable_rule(*, attrs, **kwargs):
+ return create_base_executable_rule(
+ attrs = dicts.add(BAZEL_EXECUTABLE_ATTRS, attrs),
+ fragments = ["py", "bazel_py"],
+ **kwargs
+ )
+
+def py_executable_bazel_impl(ctx, *, is_test, inherited_environment):
+ """Common code for executables for Baze."""
+ result = py_executable_base_impl(
+ ctx = ctx,
+ semantics = create_binary_semantics_bazel(),
+ is_test = is_test,
+ inherited_environment = inherited_environment,
+ )
+ return struct(
+ providers = result.providers,
+ **result.legacy_providers
+ )
+
+def create_binary_semantics_bazel():
+ return create_binary_semantics_struct(
+ # keep-sorted start
+ create_executable = _create_executable,
+ get_cc_details_for_binary = _get_cc_details_for_binary,
+ get_central_uncachable_version_file = lambda ctx: None,
+ get_coverage_deps = _get_coverage_deps,
+ get_debugger_deps = _get_debugger_deps,
+ get_extra_common_runfiles_for_binary = lambda ctx: ctx.runfiles(),
+ get_extra_providers = _get_extra_providers,
+ get_extra_write_build_data_env = lambda ctx: {},
+ get_imports = get_imports,
+ get_interpreter_path = _get_interpreter_path,
+ get_native_deps_dso_name = _get_native_deps_dso_name,
+ get_native_deps_user_link_flags = _get_native_deps_user_link_flags,
+ get_stamp_flag = _get_stamp_flag,
+ maybe_precompile = maybe_precompile,
+ should_build_native_deps_dso = lambda ctx: False,
+ should_create_init_files = _should_create_init_files,
+ should_include_build_data = lambda ctx: False,
+ # keep-sorted end
+ )
+
+def _get_coverage_deps(ctx, runtime_details):
+ _ = ctx, runtime_details # @unused
+ return []
+
+def _get_debugger_deps(ctx, runtime_details):
+ _ = ctx, runtime_details # @unused
+ return []
+
+def _get_extra_providers(ctx, main_py, runtime_details):
+ _ = ctx, main_py, runtime_details # @unused
+ return {}, []
+
+def _get_stamp_flag(ctx):
+ # NOTE: Undocumented API; private to builtins
+ return ctx.configuration.stamp_binaries
+
+def _should_create_init_files(ctx):
+ if ctx.attr.legacy_create_init == -1:
+ return not ctx.fragments.py.default_to_explicit_init_py
+ else:
+ return bool(ctx.attr.legacy_create_init)
+
+def _create_executable(
+ ctx,
+ *,
+ executable,
+ main_py,
+ imports,
+ is_test,
+ runtime_details,
+ cc_details,
+ native_deps_details,
+ runfiles_details):
+ _ = is_test, cc_details, native_deps_details # @unused
+
+ common_bootstrap_template_kwargs = dict(
+ main_py = main_py,
+ imports = imports,
+ runtime_details = runtime_details,
+ )
+
+ is_windows = target_platform_has_any_constraint(ctx, ctx.attr._windows_constraints)
+
+ if is_windows:
+ if not executable.extension == "exe":
+ fail("Should not happen: somehow we are generating a non-.exe file on windows")
+ base_executable_name = executable.basename[0:-4]
+ else:
+ base_executable_name = executable.basename
+
+ zip_bootstrap = ctx.actions.declare_file(base_executable_name + ".temp", sibling = executable)
+ zip_file = ctx.actions.declare_file(base_executable_name + ".zip", sibling = executable)
+
+ _expand_bootstrap_template(
+ ctx,
+ output = zip_bootstrap,
+ is_for_zip = True,
+ **common_bootstrap_template_kwargs
+ )
+ _create_zip_file(
+ ctx,
+ output = zip_file,
+ original_nonzip_executable = executable,
+ executable_for_zip_file = zip_bootstrap,
+ runfiles = runfiles_details.default_runfiles,
+ )
+
+ extra_files_to_build = []
+
+ # NOTE: --build_python_zip defauls to true on Windows
+ build_zip_enabled = ctx.fragments.py.build_python_zip
+
+ # When --build_python_zip is enabled, then the zip file becomes
+ # one of the default outputs.
+ if build_zip_enabled:
+ extra_files_to_build.append(zip_file)
+
+ # The logic here is a bit convoluted. Essentially, there are 3 types of
+ # executables produced:
+ # 1. (non-Windows) A bootstrap template based program.
+ # 2. (non-Windows) A self-executable zip file of a bootstrap template based program.
+ # 3. (Windows) A native Windows executable that finds and launches
+ # the actual underlying Bazel program (one of the above). Note that
+ # it implicitly assumes one of the above is located next to it, and
+ # that --build_python_zip defaults to true for Windows.
+
+ should_create_executable_zip = False
+ bootstrap_output = None
+ if not is_windows:
+ if build_zip_enabled:
+ should_create_executable_zip = True
+ else:
+ bootstrap_output = executable
+ else:
+ _create_windows_exe_launcher(
+ ctx,
+ output = executable,
+ use_zip_file = build_zip_enabled,
+ python_binary_path = runtime_details.executable_interpreter_path,
+ )
+ if not build_zip_enabled:
+ # On Windows, the main executable has an "exe" extension, so
+ # here we re-use the un-extensioned name for the bootstrap output.
+ bootstrap_output = ctx.actions.declare_file(base_executable_name)
+
+ # The launcher looks for the non-zip executable next to
+ # itself, so add it to the default outputs.
+ extra_files_to_build.append(bootstrap_output)
+
+ if should_create_executable_zip:
+ if bootstrap_output != None:
+ fail("Should not occur: bootstrap_output should not be used " +
+ "when creating an executable zip")
+ _create_executable_zip_file(ctx, output = executable, zip_file = zip_file)
+ elif bootstrap_output:
+ _expand_bootstrap_template(
+ ctx,
+ output = bootstrap_output,
+ is_for_zip = build_zip_enabled,
+ **common_bootstrap_template_kwargs
+ )
+ else:
+ # Otherwise, this should be the Windows case of launcher + zip.
+ # Double check this just to make sure.
+ if not is_windows or not build_zip_enabled:
+ fail(("Should not occur: The non-executable-zip and " +
+ "non-boostrap-template case should have windows and zip " +
+ "both true, but got " +
+ "is_windows={is_windows} " +
+ "build_zip_enabled={build_zip_enabled}").format(
+ is_windows = is_windows,
+ build_zip_enabled = build_zip_enabled,
+ ))
+
+ return create_executable_result_struct(
+ extra_files_to_build = depset(extra_files_to_build),
+ output_groups = {"python_zip_file": depset([zip_file])},
+ )
+
+def _expand_bootstrap_template(
+ ctx,
+ *,
+ output,
+ main_py,
+ imports,
+ is_for_zip,
+ runtime_details):
+ runtime = runtime_details.effective_runtime
+ if (ctx.configuration.coverage_enabled and
+ runtime and
+ runtime.coverage_tool):
+ coverage_tool_runfiles_path = "{}/{}".format(
+ ctx.workspace_name,
+ runtime.coverage_tool.short_path,
+ )
+ else:
+ coverage_tool_runfiles_path = ""
+
+ if runtime:
+ shebang = runtime.stub_shebang
+ template = runtime.bootstrap_template
+ else:
+ shebang = DEFAULT_STUB_SHEBANG
+ template = ctx.file._bootstrap_template
+
+ ctx.actions.expand_template(
+ template = template,
+ output = output,
+ substitutions = {
+ "%coverage_tool%": coverage_tool_runfiles_path,
+ "%import_all%": "True" if ctx.fragments.bazel_py.python_import_all_repositories else "False",
+ "%imports%": ":".join(imports.to_list()),
+ "%is_zipfile%": "True" if is_for_zip else "False",
+ "%main%": "{}/{}".format(
+ ctx.workspace_name,
+ main_py.short_path,
+ ),
+ "%python_binary%": runtime_details.executable_interpreter_path,
+ "%shebang%": shebang,
+ "%target%": str(ctx.label),
+ "%workspace_name%": ctx.workspace_name,
+ },
+ is_executable = True,
+ )
+
+def _create_windows_exe_launcher(
+ ctx,
+ *,
+ output,
+ python_binary_path,
+ use_zip_file):
+ launch_info = ctx.actions.args()
+ launch_info.use_param_file("%s", use_always = True)
+ launch_info.set_param_file_format("multiline")
+ launch_info.add("binary_type=Python")
+ launch_info.add(ctx.workspace_name, format = "workspace_name=%s")
+ launch_info.add(
+ "1" if py_internal.runfiles_enabled(ctx) else "0",
+ format = "symlink_runfiles_enabled=%s",
+ )
+ launch_info.add(python_binary_path, format = "python_bin_path=%s")
+ launch_info.add("1" if use_zip_file else "0", format = "use_zip_file=%s")
+
+ ctx.actions.run(
+ executable = ctx.executable._windows_launcher_maker,
+ arguments = [ctx.executable._launcher.path, launch_info, output.path],
+ inputs = [ctx.executable._launcher],
+ outputs = [output],
+ mnemonic = "PyBuildLauncher",
+ progress_message = "Creating launcher for %{label}",
+ # Needed to inherit PATH when using non-MSVC compilers like MinGW
+ use_default_shell_env = True,
+ )
+
+def _create_zip_file(ctx, *, output, original_nonzip_executable, executable_for_zip_file, runfiles):
+ workspace_name = ctx.workspace_name
+ legacy_external_runfiles = _py_builtins.get_legacy_external_runfiles(ctx)
+
+ manifest = ctx.actions.args()
+ manifest.use_param_file("@%s", use_always = True)
+ manifest.set_param_file_format("multiline")
+
+ manifest.add("__main__.py={}".format(executable_for_zip_file.path))
+ manifest.add("__init__.py=")
+ manifest.add(
+ "{}=".format(
+ _get_zip_runfiles_path("__init__.py", workspace_name, legacy_external_runfiles),
+ ),
+ )
+ for path in runfiles.empty_filenames.to_list():
+ manifest.add("{}=".format(_get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles)))
+
+ def map_zip_runfiles(file):
+ if file != original_nonzip_executable and file != output:
+ return "{}={}".format(
+ _get_zip_runfiles_path(file.short_path, workspace_name, legacy_external_runfiles),
+ file.path,
+ )
+ else:
+ return None
+
+ manifest.add_all(runfiles.files, map_each = map_zip_runfiles, allow_closure = True)
+
+ inputs = [executable_for_zip_file]
+ if _py_builtins.is_bzlmod_enabled(ctx):
+ zip_repo_mapping_manifest = ctx.actions.declare_file(
+ output.basename + ".repo_mapping",
+ sibling = output,
+ )
+ _py_builtins.create_repo_mapping_manifest(
+ ctx = ctx,
+ runfiles = runfiles,
+ output = zip_repo_mapping_manifest,
+ )
+ manifest.add("{}/_repo_mapping={}".format(
+ _ZIP_RUNFILES_DIRECTORY_NAME,
+ zip_repo_mapping_manifest.path,
+ ))
+ inputs.append(zip_repo_mapping_manifest)
+
+ for artifact in runfiles.files.to_list():
+ # Don't include the original executable because it isn't used by the
+ # zip file, so no need to build it for the action.
+ # Don't include the zipfile itself because it's an output.
+ if artifact != original_nonzip_executable and artifact != output:
+ inputs.append(artifact)
+
+ zip_cli_args = ctx.actions.args()
+ zip_cli_args.add("cC")
+ zip_cli_args.add(output)
+
+ ctx.actions.run(
+ executable = ctx.executable._zipper,
+ arguments = [zip_cli_args, manifest],
+ inputs = depset(inputs),
+ outputs = [output],
+ use_default_shell_env = True,
+ mnemonic = "PythonZipper",
+ progress_message = "Building Python zip: %{label}",
+ )
+
+def _get_zip_runfiles_path(path, workspace_name, legacy_external_runfiles):
+ if legacy_external_runfiles and path.startswith(_EXTERNAL_PATH_PREFIX):
+ zip_runfiles_path = paths.relativize(path, _EXTERNAL_PATH_PREFIX)
+ else:
+ # NOTE: External runfiles (artifacts in other repos) will have a leading
+ # path component of "../" so that they refer outside the main workspace
+ # directory and into the runfiles root. By normalizing, we simplify e.g.
+ # "workspace/../foo/bar" to simply "foo/bar".
+ zip_runfiles_path = paths.normalize("{}/{}".format(workspace_name, path))
+ return "{}/{}".format(_ZIP_RUNFILES_DIRECTORY_NAME, zip_runfiles_path)
+
+def _create_executable_zip_file(ctx, *, output, zip_file):
+ ctx.actions.run_shell(
+ command = "echo '{shebang}' | cat - {zip} > {output}".format(
+ shebang = "#!/usr/bin/env python3",
+ zip = zip_file.path,
+ output = output.path,
+ ),
+ inputs = [zip_file],
+ outputs = [output],
+ use_default_shell_env = True,
+ mnemonic = "BuildBinary",
+ progress_message = "Build Python zip executable: %{label}",
+ )
+
+def _get_cc_details_for_binary(ctx, extra_deps):
+ cc_info = collect_cc_info(ctx, extra_deps = extra_deps)
+ return create_cc_details_struct(
+ cc_info_for_propagating = cc_info,
+ cc_info_for_self_link = cc_info,
+ cc_info_with_extra_link_time_libraries = None,
+ extra_runfiles = ctx.runfiles(),
+ # Though the rules require the CcToolchain, it isn't actually used.
+ cc_toolchain = None,
+ )
+
+def _get_interpreter_path(ctx, *, runtime, flag_interpreter_path):
+ if runtime:
+ if runtime.interpreter_path:
+ interpreter_path = runtime.interpreter_path
+ else:
+ interpreter_path = "{}/{}".format(
+ ctx.workspace_name,
+ runtime.interpreter.short_path,
+ )
+
+ # NOTE: External runfiles (artifacts in other repos) will have a
+ # leading path component of "../" so that they refer outside the
+ # main workspace directory and into the runfiles root. By
+ # normalizing, we simplify e.g. "workspace/../foo/bar" to simply
+ # "foo/bar"
+ interpreter_path = paths.normalize(interpreter_path)
+
+ elif flag_interpreter_path:
+ interpreter_path = flag_interpreter_path
+ else:
+ fail("Unable to determine interpreter path")
+
+ return interpreter_path
+
+def _get_native_deps_dso_name(ctx):
+ _ = ctx # @unused
+ fail("Building native deps DSO not supported.")
+
+def _get_native_deps_user_link_flags(ctx):
+ _ = ctx # @unused
+ fail("Building native deps DSO not supported.")
diff --git a/python/private/common/py_internal.bzl b/python/private/common/py_internal.bzl
new file mode 100644
index 0000000000..429637253f
--- /dev/null
+++ b/python/private/common/py_internal.bzl
@@ -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.
+"""PYTHON RULE IMPLEMENTATION ONLY: Do not use outside of the rule implementations and their tests.
+
+Re-exports the restricted-use py_internal helper under its original name.
+
+These may change at any time and are closely coupled to the rule implementation.
+"""
+
+# The py_internal global is only available in Bazel 7+, so loading of it
+# must go through a repo rule with Bazel version detection logic.
+load("@rules_python_internal//:py_internal.bzl", "py_internal_impl")
+
+# NOTE: This is None prior to Bazel 7, as set by @rules_python_internal
+py_internal = py_internal_impl
diff --git a/python/private/common/py_library.bzl b/python/private/common/py_library.bzl
new file mode 100644
index 0000000000..8d09c51092
--- /dev/null
+++ b/python/private/common/py_library.bzl
@@ -0,0 +1,101 @@
+# 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.
+"""Implementation of py_library rule."""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load(
+ ":attributes.bzl",
+ "COMMON_ATTRS",
+ "PY_SRCS_ATTRS",
+ "SRCS_VERSION_ALL_VALUES",
+ "create_srcs_attr",
+ "create_srcs_version_attr",
+)
+load(
+ ":common.bzl",
+ "check_native_allowed",
+ "collect_imports",
+ "collect_runfiles",
+ "create_instrumented_files_info",
+ "create_output_group_info",
+ "create_py_info",
+ "filter_to_py_srcs",
+ "union_attrs",
+)
+load(":providers.bzl", "PyCcLinkParamsProvider")
+load(":py_internal.bzl", "py_internal")
+
+_py_builtins = py_internal
+
+LIBRARY_ATTRS = union_attrs(
+ COMMON_ATTRS,
+ PY_SRCS_ATTRS,
+ create_srcs_version_attr(values = SRCS_VERSION_ALL_VALUES),
+ create_srcs_attr(mandatory = False),
+)
+
+def py_library_impl(ctx, *, semantics):
+ """Abstract implementation of py_library rule.
+
+ Args:
+ ctx: The rule ctx
+ semantics: A `LibrarySemantics` struct; see `create_library_semantics_struct`
+
+ Returns:
+ A list of modern providers to propagate.
+ """
+ check_native_allowed(ctx)
+ direct_sources = filter_to_py_srcs(ctx.files.srcs)
+ output_sources = depset(semantics.maybe_precompile(ctx, direct_sources))
+ runfiles = collect_runfiles(ctx = ctx, files = output_sources)
+
+ cc_info = semantics.get_cc_info_for_library(ctx)
+ py_info, deps_transitive_sources = create_py_info(
+ ctx,
+ direct_sources = depset(direct_sources),
+ imports = collect_imports(ctx, semantics),
+ )
+
+ # TODO(b/253059598): Remove support for extra actions; https://github.com/bazelbuild/bazel/issues/16455
+ listeners_enabled = _py_builtins.are_action_listeners_enabled(ctx)
+ if listeners_enabled:
+ _py_builtins.add_py_extra_pseudo_action(
+ ctx = ctx,
+ dependency_transitive_python_sources = deps_transitive_sources,
+ )
+
+ return [
+ DefaultInfo(files = output_sources, runfiles = runfiles),
+ py_info,
+ create_instrumented_files_info(ctx),
+ PyCcLinkParamsProvider(cc_info = cc_info),
+ create_output_group_info(py_info.transitive_sources, extra_groups = {}),
+ ]
+
+def create_py_library_rule(*, attrs = {}, **kwargs):
+ """Creates a py_library rule.
+
+ Args:
+ attrs: dict of rule attributes.
+ **kwargs: Additional kwargs to pass onto the rule() call.
+ Returns:
+ A rule object
+ """
+ return rule(
+ attrs = dicts.add(LIBRARY_ATTRS, attrs),
+ # TODO(b/253818097): fragments=py is only necessary so that
+ # RequiredConfigFragmentsTest passes
+ fragments = ["py"],
+ **kwargs
+ )
diff --git a/python/private/common/py_library_macro_bazel.bzl b/python/private/common/py_library_macro_bazel.bzl
new file mode 100644
index 0000000000..b4f51eff1d
--- /dev/null
+++ b/python/private/common/py_library_macro_bazel.bzl
@@ -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.
+"""Implementation of macro-half of py_library rule."""
+
+load(":py_library_rule_bazel.bzl", py_library_rule = "py_library")
+
+def py_library(**kwargs):
+ py_library_rule(**kwargs)
diff --git a/python/private/common/py_library_rule_bazel.bzl b/python/private/common/py_library_rule_bazel.bzl
new file mode 100644
index 0000000000..453abcb816
--- /dev/null
+++ b/python/private/common/py_library_rule_bazel.bzl
@@ -0,0 +1,47 @@
+# 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.
+"""Implementation of py_library for Bazel."""
+
+load(":attributes_bazel.bzl", "IMPORTS_ATTRS")
+load(":common.bzl", "create_library_semantics_struct", "union_attrs")
+load(":common_bazel.bzl", "collect_cc_info", "get_imports", "maybe_precompile")
+load(
+ ":py_library.bzl",
+ "LIBRARY_ATTRS",
+ "create_py_library_rule",
+ bazel_py_library_impl = "py_library_impl",
+)
+
+_BAZEL_LIBRARY_ATTRS = union_attrs(
+ LIBRARY_ATTRS,
+ IMPORTS_ATTRS,
+)
+
+def create_library_semantics_bazel():
+ return create_library_semantics_struct(
+ get_imports = get_imports,
+ maybe_precompile = maybe_precompile,
+ get_cc_info_for_library = collect_cc_info,
+ )
+
+def _py_library_impl(ctx):
+ return bazel_py_library_impl(
+ ctx,
+ semantics = create_library_semantics_bazel(),
+ )
+
+py_library = create_py_library_rule(
+ implementation = _py_library_impl,
+ attrs = _BAZEL_LIBRARY_ATTRS,
+)
diff --git a/python/private/common/py_runtime_macro.bzl b/python/private/common/py_runtime_macro.bzl
new file mode 100644
index 0000000000..7d04388fd6
--- /dev/null
+++ b/python/private/common/py_runtime_macro.bzl
@@ -0,0 +1,22 @@
+# 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.
+"""Macro to wrap the py_runtime rule."""
+
+load(":py_runtime_rule.bzl", py_runtime_rule = "py_runtime")
+
+# NOTE: The function name is purposefully selected to match the underlying
+# rule name so that e.g. 'generator_function' shows as the same name so
+# that it is less confusing to users.
+def py_runtime(**kwargs):
+ py_runtime_rule(**kwargs)
diff --git a/python/private/common/py_runtime_rule.bzl b/python/private/common/py_runtime_rule.bzl
new file mode 100644
index 0000000000..39434042ea
--- /dev/null
+++ b/python/private/common/py_runtime_rule.bzl
@@ -0,0 +1,216 @@
+# 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.
+"""Implementation of py_runtime rule."""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load("@bazel_skylib//lib:paths.bzl", "paths")
+load(":attributes.bzl", "NATIVE_RULES_ALLOWLIST_ATTRS")
+load(":common.bzl", "check_native_allowed")
+load(":providers.bzl", "DEFAULT_BOOTSTRAP_TEMPLATE", "DEFAULT_STUB_SHEBANG", _PyRuntimeInfo = "PyRuntimeInfo")
+load(":py_internal.bzl", "py_internal")
+
+_py_builtins = py_internal
+
+def _py_runtime_impl(ctx):
+ check_native_allowed(ctx)
+ interpreter_path = ctx.attr.interpreter_path or None # Convert empty string to None
+ interpreter = ctx.file.interpreter
+ if (interpreter_path and interpreter) or (not interpreter_path and not interpreter):
+ fail("exactly one of the 'interpreter' or 'interpreter_path' attributes must be specified")
+
+ runtime_files = depset(transitive = [
+ t[DefaultInfo].files
+ for t in ctx.attr.files
+ ])
+
+ hermetic = bool(interpreter)
+ if not hermetic:
+ if runtime_files:
+ fail("if 'interpreter_path' is given then 'files' must be empty")
+ if not paths.is_absolute(interpreter_path):
+ fail("interpreter_path must be an absolute path")
+
+ if ctx.attr.coverage_tool:
+ coverage_di = ctx.attr.coverage_tool[DefaultInfo]
+
+ if _py_builtins.is_singleton_depset(coverage_di.files):
+ coverage_tool = coverage_di.files.to_list()[0]
+ elif coverage_di.files_to_run and coverage_di.files_to_run.executable:
+ coverage_tool = coverage_di.files_to_run.executable
+ else:
+ fail("coverage_tool must be an executable target or must produce exactly one file.")
+
+ coverage_files = depset(transitive = [
+ coverage_di.files,
+ coverage_di.default_runfiles.files,
+ ])
+ else:
+ coverage_tool = None
+ coverage_files = None
+
+ python_version = ctx.attr.python_version
+ if python_version == "_INTERNAL_SENTINEL":
+ if ctx.fragments.py.use_toolchains:
+ fail(
+ "When using Python toolchains, this attribute must be set explicitly to either 'PY2' " +
+ "or 'PY3'. See https://github.com/bazelbuild/bazel/issues/7899 for more " +
+ "information. You can temporarily avoid this error by reverting to the legacy " +
+ "Python runtime mechanism (`--incompatible_use_python_toolchains=false`).",
+ )
+ else:
+ python_version = ctx.fragments.py.default_python_version
+
+ # TODO: Uncomment this after --incompatible_python_disable_py2 defaults to true
+ # if ctx.fragments.py.disable_py2 and python_version == "PY2":
+ # fail("Using Python 2 is not supported and disabled; see " +
+ # "https://github.com/bazelbuild/bazel/issues/15684")
+
+ return [
+ _PyRuntimeInfo(
+ interpreter_path = interpreter_path or None,
+ interpreter = interpreter,
+ files = runtime_files if hermetic else None,
+ coverage_tool = coverage_tool,
+ coverage_files = coverage_files,
+ python_version = python_version,
+ stub_shebang = ctx.attr.stub_shebang,
+ bootstrap_template = ctx.file.bootstrap_template,
+ ),
+ DefaultInfo(
+ files = runtime_files,
+ runfiles = ctx.runfiles(),
+ ),
+ ]
+
+# Bind to the name "py_runtime" to preserve the kind/rule_class it shows up
+# as elsewhere.
+py_runtime = rule(
+ implementation = _py_runtime_impl,
+ doc = """
+Represents a Python runtime used to execute Python code.
+
+A `py_runtime` target can represent 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 an executable target that acts as
+the interpreter. In both cases, an "interpreter" means 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.
+
+A platform runtime is by its nature non-hermetic. It imposes a requirement on
+the target platform to have an interpreter located at a specific path. An
+in-build runtime may or may not be hermetic, depending on whether it points to
+a checked-in interpreter or a wrapper script that accesses the system
+interpreter.
+
+# Example
+
+```
+py_runtime(
+ name = "python-2.7.12",
+ files = glob(["python-2.7.12/**"]),
+ interpreter = "python-2.7.12/bin/python",
+)
+
+py_runtime(
+ name = "python-3.6.0",
+ interpreter_path = "/opt/pyenv/versions/3.6.0/bin/python",
+)
+```
+""",
+ fragments = ["py"],
+ attrs = dicts.add(NATIVE_RULES_ALLOWLIST_ATTRS, {
+ "bootstrap_template": attr.label(
+ allow_single_file = True,
+ default = DEFAULT_BOOTSTRAP_TEMPLATE,
+ doc = """
+The bootstrap script template file to use. Should have %python_binary%,
+%workspace_name%, %main%, and %imports%.
+
+This template, after expansion, becomes the executable file used to start the
+process, so it is responsible for initial bootstrapping actions such as finding
+the Python interpreter, runfiles, and constructing an environment to run the
+intended Python application.
+
+While this attribute is currently optional, it will become required when the
+Python rules are moved out of Bazel itself.
+
+The exact variable names expanded is an unstable API and is subject to change.
+The API will become more stable when the Python rules are moved out of Bazel
+itself.
+
+See @bazel_tools//tools/python:python_bootstrap_template.txt for more variables.
+""",
+ ),
+ "coverage_tool": attr.label(
+ allow_files = False,
+ 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.
+""",
+ ),
+ "files": attr.label_list(
+ allow_files = True,
+ doc = """
+For an in-build runtime, this is the set of files comprising this runtime.
+These files will be added to the runfiles of Python binaries that use this
+runtime. For a platform runtime this attribute must not be set.
+""",
+ ),
+ "interpreter": attr.label(
+ allow_single_file = True,
+ doc = """
+For an in-build runtime, this is the target to invoke as the interpreter. For a
+platform runtime this attribute must not be set.
+""",
+ ),
+ "interpreter_path": attr.string(doc = """
+For a platform runtime, this is the absolute path of a Python interpreter on
+the target platform. For an in-build runtime this attribute must not be set.
+"""),
+ "python_version": attr.string(
+ default = "_INTERNAL_SENTINEL",
+ values = ["PY2", "PY3", "_INTERNAL_SENTINEL"],
+ doc = """
+Whether this runtime is for Python major version 2 or 3. Valid values are `"PY2"`
+and `"PY3"`.
+
+The default value is controlled by the `--incompatible_py3_is_default` flag.
+However, in the future this attribute will be mandatory and have no default
+value.
+ """,
+ ),
+ "stub_shebang": attr.string(
+ default = DEFAULT_STUB_SHEBANG,
+ doc = """
+"Shebang" expression prepended to the bootstrapping Python stub script
+used when executing `py_binary` targets.
+
+See https://github.com/bazelbuild/bazel/issues/8685 for
+motivation.
+
+Does not apply to Windows.
+""",
+ ),
+ }),
+)
diff --git a/python/private/common/py_test_macro_bazel.bzl b/python/private/common/py_test_macro_bazel.bzl
new file mode 100644
index 0000000000..24b78fef96
--- /dev/null
+++ b/python/private/common/py_test_macro_bazel.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.
+"""Implementation of macro-half of py_test rule."""
+
+load(":common_bazel.bzl", "convert_legacy_create_init_to_int")
+load(":py_test_rule_bazel.bzl", py_test_rule = "py_test")
+
+def py_test(**kwargs):
+ convert_legacy_create_init_to_int(kwargs)
+ py_test_rule(**kwargs)
diff --git a/python/private/common/py_test_rule_bazel.bzl b/python/private/common/py_test_rule_bazel.bzl
new file mode 100644
index 0000000000..348935edee
--- /dev/null
+++ b/python/private/common/py_test_rule_bazel.bzl
@@ -0,0 +1,56 @@
+# 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.
+"""Rule implementation of py_test for Bazel."""
+
+load("@bazel_skylib//lib:dicts.bzl", "dicts")
+load(":attributes.bzl", "AGNOSTIC_TEST_ATTRS")
+load(":common.bzl", "maybe_add_test_execution_info")
+load(
+ ":py_executable_bazel.bzl",
+ "create_executable_rule",
+ "py_executable_bazel_impl",
+)
+load(":semantics.bzl", "TOOLS_REPO")
+
+_BAZEL_PY_TEST_ATTRS = {
+ # This *might* be a magic attribute to help C++ coverage work. There's no
+ # docs about this; see TestActionBuilder.java
+ "_collect_cc_coverage": attr.label(
+ default = "@" + TOOLS_REPO + "//tools/test:collect_cc_coverage",
+ executable = True,
+ cfg = "exec",
+ ),
+ # This *might* be a magic attribute to help C++ coverage work. There's no
+ # docs about this; see TestActionBuilder.java
+ "_lcov_merger": attr.label(
+ default = configuration_field(fragment = "coverage", name = "output_generator"),
+ cfg = "exec",
+ executable = True,
+ ),
+}
+
+def _py_test_impl(ctx):
+ providers = py_executable_bazel_impl(
+ ctx = ctx,
+ is_test = True,
+ inherited_environment = ctx.attr.env_inherit,
+ )
+ maybe_add_test_execution_info(providers.providers, ctx)
+ return providers
+
+py_test = create_executable_rule(
+ implementation = _py_test_impl,
+ attrs = dicts.add(AGNOSTIC_TEST_ATTRS, _BAZEL_PY_TEST_ATTRS),
+ test = True,
+)
diff --git a/python/private/common/semantics.bzl b/python/private/common/semantics.bzl
new file mode 100644
index 0000000000..487ff303ef
--- /dev/null
+++ b/python/private/common/semantics.bzl
@@ -0,0 +1,34 @@
+# 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.
+"""Contains constants that vary between Bazel and Google-internal"""
+
+IMPORTS_ATTR_SUPPORTED = True
+
+TOOLS_REPO = "bazel_tools"
+PLATFORMS_LOCATION = "@platforms/"
+
+SRCS_ATTR_ALLOW_FILES = [".py", ".py3"]
+
+DEPS_ATTR_ALLOW_RULES = None
+
+PY_RUNTIME_ATTR_NAME = "_py_interpreter"
+
+BUILD_DATA_SYMLINK_PATH = None
+
+IS_BAZEL = True
+
+NATIVE_RULES_MIGRATION_HELP_URL = "https://github.com/bazelbuild/bazel/issues/17773"
+NATIVE_RULES_MIGRATION_FIX_CMD = "add_python_loads"
+
+ALLOWED_MAIN_EXTENSIONS = [".py"]
diff --git a/python/private/full_version.bzl b/python/private/full_version.bzl
new file mode 100644
index 0000000000..68c969416e
--- /dev/null
+++ b/python/private/full_version.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.
+
+"""A small helper to ensure that we are working with full versions."""
+
+load("//python:versions.bzl", "MINOR_MAPPING")
+
+def full_version(version):
+ """Return a full version.
+
+ Args:
+ version: the version in `X.Y` or `X.Y.Z` format.
+
+ Returns:
+ a full version given the version string. If the string is already a
+ major version then we return it as is.
+ """
+ if version in MINOR_MAPPING:
+ return MINOR_MAPPING[version]
+
+ parts = version.split(".")
+ if len(parts) == 3:
+ return version
+ elif len(parts) == 2:
+ fail(
+ "Unknown Python version '{}', available values are: {}".format(
+ version,
+ ",".join(MINOR_MAPPING.keys()),
+ ),
+ )
+ else:
+ fail("Unknown version format: {}".format(version))
diff --git a/python/private/internal_config_repo.bzl b/python/private/internal_config_repo.bzl
new file mode 100644
index 0000000000..cfc7616de9
--- /dev/null
+++ b/python/private/internal_config_repo.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.
+"""Repository to generate configuration settings info from the environment.
+
+This handles settings that can't be encoded as regular build configuration flags,
+such as globals available to Bazel versions, or propagating user environment
+settings for rules to later use.
+"""
+
+load("//python/private:bzlmod_enabled.bzl", "BZLMOD_ENABLED")
+
+_ENABLE_PYSTAR_ENVVAR_NAME = "RULES_PYTHON_ENABLE_PYSTAR"
+_ENABLE_PYSTAR_DEFAULT = "0"
+
+_CONFIG_TEMPLATE = """\
+config = struct(
+ enable_pystar = {enable_pystar},
+)
+"""
+
+# The py_internal symbol is only accessible from within @rules_python, so we have to
+# load it from there and re-export it so that rules_python can later load it.
+_PY_INTERNAL_SHIM = """\
+load("@rules_python//tools/build_defs/python/private:py_internal_renamed.bzl", "py_internal_renamed")
+py_internal_impl = py_internal_renamed
+"""
+
+ROOT_BUILD_TEMPLATE = """\
+load("@bazel_skylib//:bzl_library.bzl", "bzl_library")
+
+package(
+ default_visibility = [
+ "{visibility}",
+ ]
+)
+
+bzl_library(
+ name = "rules_python_config_bzl",
+ srcs = ["rules_python_config.bzl"]
+)
+
+bzl_library(
+ name = "py_internal_bzl",
+ srcs = ["py_internal.bzl"],
+ deps = [{py_internal_dep}],
+)
+"""
+
+def _internal_config_repo_impl(rctx):
+ enable_pystar = _bool_from_environ(rctx, _ENABLE_PYSTAR_ENVVAR_NAME, _ENABLE_PYSTAR_DEFAULT)
+ rctx.file("rules_python_config.bzl", _CONFIG_TEMPLATE.format(
+ enable_pystar = enable_pystar,
+ ))
+
+ if enable_pystar or (
+ # Bazel 7+ (dev and later) has native.starlark_doc_extract, and thus the py_internal global
+ hasattr(native, "starlark_doc_extract") and
+ # The logic to allow the symbol doesn't work properly under bzlmod,
+ # even if the symbol is otherwise functional.
+ not BZLMOD_ENABLED
+ ):
+ shim_content = _PY_INTERNAL_SHIM
+ py_internal_dep = '"@rules_python//tools/build_defs/python/private:py_internal_renamed_bzl"'
+ else:
+ shim_content = "py_internal_impl = None\n"
+ py_internal_dep = ""
+
+ # Bazel 5 doesn't support repository visibility, so just use public
+ # as a stand-in
+ if native.bazel_version.startswith("5."):
+ visibility = "//visibility:public"
+ else:
+ visibility = "@rules_python//:__subpackages__"
+
+ rctx.file("BUILD", ROOT_BUILD_TEMPLATE.format(
+ py_internal_dep = py_internal_dep,
+ visibility = visibility,
+ ))
+ rctx.file("py_internal.bzl", shim_content)
+ return None
+
+internal_config_repo = repository_rule(
+ implementation = _internal_config_repo_impl,
+ environ = [_ENABLE_PYSTAR_ENVVAR_NAME],
+)
+
+def _bool_from_environ(rctx, key, default):
+ return bool(int(rctx.os.environ.get(key, default)))
diff --git a/python/private/py_console_script_binary.bzl b/python/private/py_console_script_binary.bzl
new file mode 100644
index 0000000000..bd992a8f75
--- /dev/null
+++ b/python/private/py_console_script_binary.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.
+
+"""
+Implementation for the macro to generate a console_script py_binary from an 'entry_points.txt' config.
+"""
+
+load("//python:py_binary.bzl", "py_binary")
+load(":py_console_script_gen.bzl", "py_console_script_gen")
+
+def _dist_info(pkg):
+ """Return the first candidate for the dist_info target label.
+
+ We cannot do `Label(pkg)` here because the string will be evaluated within
+ the context of the rules_python repo_mapping and it will fail because
+ rules_python does not know anything about the hub repos that the user has
+ available.
+
+ NOTE: Works with `incompatible_generate_aliases` and without by assuming the
+ following formats:
+ * @pypi_pylint//:pkg
+ * @pypi//pylint
+ * @pypi//pylint:pkg
+ * Label("@pypi//pylint:pkg")
+ """
+
+ # str() is called to convert Label objects
+ return str(pkg).replace(":pkg", "") + ":dist_info"
+
+def py_console_script_binary(
+ *,
+ name,
+ pkg,
+ entry_points_txt = None,
+ script = None,
+ binary_rule = py_binary,
+ **kwargs):
+ """Generate a py_binary for a console_script entry_point.
+
+ Args:
+ name: str, The name of the resulting target.
+ pkg: target, the package for which to generate the script.
+ entry_points_txt: optional target, the entry_points.txt file to parse
+ for available console_script values. It may be a single file, or a
+ group of files, but must contain a file named `entry_points.txt`.
+ If not specified, defaults to the `dist_info` target in the same
+ package as the `pkg` Label.
+ script: str, The console script name that the py_binary is going to be
+ generated for. Defaults to the normalized name attribute.
+ binary_rule: callable, The rule/macro to use to instantiate
+ the target. It's expected to behave like `py_binary`.
+ Defaults to @rules_python//python:py_binary.bzl#py_binary.
+ **kwargs: Extra parameters forwarded to binary_rule.
+ """
+ main = "rules_python_entry_point_{}.py".format(name)
+
+ if kwargs.pop("srcs", None):
+ fail("passing 'srcs' attribute to py_console_script_binary is unsupported")
+
+ py_console_script_gen(
+ name = "_{}_gen".format(name),
+ # NOTE @aignas 2023-08-05: Works with `incompatible_generate_aliases` and without.
+ entry_points_txt = entry_points_txt or _dist_info(pkg),
+ out = main,
+ console_script = script,
+ console_script_guess = name,
+ visibility = ["//visibility:private"],
+ )
+
+ binary_rule(
+ name = name,
+ srcs = [main],
+ main = main,
+ deps = [pkg] + kwargs.pop("deps", []),
+ **kwargs
+ )
diff --git a/python/private/py_console_script_gen.bzl b/python/private/py_console_script_gen.bzl
new file mode 100644
index 0000000000..7dd4dd2dad
--- /dev/null
+++ b/python/private/py_console_script_gen.bzl
@@ -0,0 +1,93 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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 private rule to generate an entry_point python file to be used in a py_binary.
+
+Right now it only supports console_scripts via the entry_points.txt file in the dist-info.
+
+NOTE @aignas 2023-08-07: This cannot be in pure starlark, because we need to
+read a file and then create a `.py` file based on the contents of that file,
+which cannot be done in pure starlark according to
+https://github.com/bazelbuild/bazel/issues/14744
+"""
+
+_ENTRY_POINTS_TXT = "entry_points.txt"
+
+def _get_entry_points_txt(entry_points_txt):
+ """Get the entry_points.txt file
+
+ TODO: use map_each to avoid flattening of the directories outside the execution phase.
+ """
+ for file in entry_points_txt.files.to_list():
+ if file.basename == _ENTRY_POINTS_TXT:
+ return file
+
+ fail("{} does not contain {}".format(entry_points_txt, _ENTRY_POINTS_TXT))
+
+def _py_console_script_gen_impl(ctx):
+ entry_points_txt = _get_entry_points_txt(ctx.attr.entry_points_txt)
+
+ args = ctx.actions.args()
+ args.add("--console-script", ctx.attr.console_script)
+ args.add("--console-script-guess", ctx.attr.console_script_guess)
+ args.add(entry_points_txt)
+ args.add(ctx.outputs.out)
+
+ ctx.actions.run(
+ inputs = [
+ entry_points_txt,
+ ],
+ outputs = [ctx.outputs.out],
+ arguments = [args],
+ mnemonic = "PyConsoleScriptBinaryGen",
+ progress_message = "Generating py_console_script_binary main: %{label}",
+ executable = ctx.executable._tool,
+ )
+
+ return [DefaultInfo(
+ files = depset([ctx.outputs.out]),
+ )]
+
+py_console_script_gen = rule(
+ _py_console_script_gen_impl,
+ attrs = {
+ "console_script": attr.string(
+ doc = "The name of the console_script to create the .py file for. Optional if there is only a single entry-point available.",
+ default = "",
+ mandatory = False,
+ ),
+ "console_script_guess": attr.string(
+ doc = "The string used for guessing the console_script if it is not provided.",
+ default = "",
+ mandatory = False,
+ ),
+ "entry_points_txt": attr.label(
+ doc = "The filegroup to search for entry_points.txt.",
+ mandatory = True,
+ ),
+ "out": attr.output(
+ doc = "Output file location.",
+ mandatory = True,
+ ),
+ "_tool": attr.label(
+ default = ":py_console_script_gen_py",
+ executable = True,
+ cfg = "exec",
+ ),
+ },
+ doc = """\
+Builds an entry_point script from an entry_points.txt file.
+""",
+)
diff --git a/python/private/py_console_script_gen.py b/python/private/py_console_script_gen.py
new file mode 100644
index 0000000000..30e93c2e5b
--- /dev/null
+++ b/python/private/py_console_script_gen.py
@@ -0,0 +1,180 @@
+# Copyright 2023 The Bazel Authors. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""
+console_script generator from entry_points.txt contents.
+
+For Python versions earlier than 3.11 and for earlier bazel versions than 7.0 we need to workaround the issue of
+sys.path[0] breaking out of the runfiles tree see the following for more context:
+* https://github.com/bazelbuild/rules_python/issues/382
+* https://github.com/bazelbuild/bazel/pull/15701
+
+In affected bazel and Python versions we see in programs such as `flake8`, `pylint` or `pytest` errors because the
+first `sys.path` element is outside the `runfiles` directory and if the `name` of the `py_binary` is the same as
+the program name, then the script (e.g. `flake8`) will start failing whilst trying to import its own internals from
+the bazel entrypoint script.
+
+The mitigation strategy is to remove the first entry in the `sys.path` if it does not have `.runfiles` and it seems
+to fix the behaviour of console_scripts under `bazel run`.
+
+This would not happen if we created an console_script binary in the root of an external repository, e.g.
+`@pypi_pylint//` because the path for the external repository is already in the runfiles directory.
+"""
+
+from __future__ import annotations
+
+import argparse
+import configparser
+import pathlib
+import re
+import sys
+import textwrap
+
+_ENTRY_POINTS_TXT = "entry_points.txt"
+
+_TEMPLATE = """\
+import sys
+
+# See @rules_python//python/private:py_console_script_gen.py for explanation
+if getattr(sys.flags, "safe_path", False):
+ # We are running on Python 3.11 and we don't need this workaround
+ pass
+elif ".runfiles" not in sys.path[0]:
+ sys.path = sys.path[1:]
+
+try:
+ from {module} import {attr}
+except ImportError:
+ entries = "\\n".join(sys.path)
+ print("Printing sys.path entries for easier debugging:", file=sys.stderr)
+ print(f"sys.path is:\\n{{entries}}", file=sys.stderr)
+ raise
+
+if __name__ == "__main__":
+ sys.exit({entry_point}())
+"""
+
+
+class EntryPointsParser(configparser.ConfigParser):
+ """A class handling entry_points.txt
+
+ See https://packaging.python.org/en/latest/specifications/entry-points/
+ """
+
+ optionxform = staticmethod(str)
+
+
+def _guess_entry_point(guess: str, console_scripts: dict[string, string]) -> str | None:
+ for key, candidate in console_scripts.items():
+ if guess == key:
+ return candidate
+
+
+def run(
+ *,
+ entry_points: pathlib.Path,
+ out: pathlib.Path,
+ console_script: str,
+ console_script_guess: str,
+):
+ """Run the generator
+
+ Args:
+ entry_points: The entry_points.txt file to be parsed.
+ out: The output file.
+ console_script: The console_script entry in the entry_points.txt file.
+ """
+ config = EntryPointsParser()
+ config.read(entry_points)
+
+ try:
+ console_scripts = dict(config["console_scripts"])
+ except KeyError:
+ raise RuntimeError(
+ f"The package does not provide any console_scripts in it's {_ENTRY_POINTS_TXT}"
+ )
+
+ if console_script:
+ try:
+ entry_point = console_scripts[console_script]
+ except KeyError:
+ available = ", ".join(sorted(console_scripts.keys()))
+ raise RuntimeError(
+ f"The console_script '{console_script}' was not found, only the following are available: {available}"
+ ) from None
+ else:
+ # Get rid of the extension and the common prefix
+ entry_point = _guess_entry_point(
+ guess=console_script_guess,
+ console_scripts=console_scripts,
+ )
+
+ if not entry_point:
+ available = ", ".join(sorted(console_scripts.keys()))
+ raise RuntimeError(
+ f"Tried to guess that you wanted '{console_script_guess}', but could not find it. "
+ f"Please select one of the following console scripts: {available}"
+ ) from None
+
+ module, _, entry_point = entry_point.rpartition(":")
+ attr, _, _ = entry_point.partition(".")
+ # TODO: handle 'extras' in entry_point generation
+ # See https://github.com/bazelbuild/rules_python/issues/1383
+ # See https://packaging.python.org/en/latest/specifications/entry-points/
+
+ with open(out, "w") as f:
+ f.write(
+ _TEMPLATE.format(
+ module=module,
+ attr=attr,
+ entry_point=entry_point,
+ ),
+ )
+
+
+def main():
+ parser = argparse.ArgumentParser(description="console_script generator")
+ parser.add_argument(
+ "--console-script",
+ help="The console_script to generate the entry_point template for.",
+ )
+ parser.add_argument(
+ "--console-script-guess",
+ required=True,
+ help="The string used for guessing the console_script if it is not provided.",
+ )
+ parser.add_argument(
+ "entry_points",
+ metavar="ENTRY_POINTS_TXT",
+ type=pathlib.Path,
+ help="The entry_points.txt within the dist-info of a PyPI wheel",
+ )
+ parser.add_argument(
+ "out",
+ type=pathlib.Path,
+ metavar="OUT",
+ help="The output file.",
+ )
+ args = parser.parse_args()
+
+ run(
+ entry_points=args.entry_points,
+ out=args.out,
+ console_script=args.console_script,
+ console_script_guess=args.console_script_guess,
+ )
+
+
+if __name__ == "__main__":
+ main()
diff --git a/python/private/py_runtime_pair_macro.bzl b/python/private/py_runtime_pair_macro.bzl
new file mode 100644
index 0000000000..3cc359968e
--- /dev/null
+++ b/python/private/py_runtime_pair_macro.bzl
@@ -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.
+
+"""Implementation of py_runtime_pair macro portion."""
+
+load(":py_runtime_pair_rule.bzl", _py_runtime_pair = "py_runtime_pair")
+
+# 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_runtime_pair(**kwargs):
+ """Creates a py_runtime_pair target.
+
+ Args:
+ **kwargs: Keyword args to pass onto underlying rule.
+ """
+ _py_runtime_pair(**kwargs)
diff --git a/python/private/py_runtime_pair_rule.bzl b/python/private/py_runtime_pair_rule.bzl
new file mode 100644
index 0000000000..d0d8c5b5ee
--- /dev/null
+++ b/python/private/py_runtime_pair_rule.bzl
@@ -0,0 +1,138 @@
+# 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.
+
+"""Implementation of py_runtime_pair."""
+
+load("//python:py_runtime_info.bzl", "PyRuntimeInfo")
+
+def _py_runtime_pair_impl(ctx):
+ if ctx.attr.py2_runtime != None:
+ py2_runtime = ctx.attr.py2_runtime[PyRuntimeInfo]
+ if py2_runtime.python_version != "PY2":
+ fail("The Python runtime in the 'py2_runtime' attribute did not have " +
+ "version 'PY2'")
+ else:
+ py2_runtime = None
+
+ if ctx.attr.py3_runtime != None:
+ py3_runtime = ctx.attr.py3_runtime[PyRuntimeInfo]
+ if py3_runtime.python_version != "PY3":
+ fail("The Python runtime in the 'py3_runtime' attribute did not have " +
+ "version 'PY3'")
+ else:
+ py3_runtime = None
+
+ # TODO: Uncomment this after --incompatible_python_disable_py2 defaults to true
+ # if _is_py2_disabled(ctx) and py2_runtime != None:
+ # fail("Using Python 2 is not supported and disabled; see " +
+ # "https://github.com/bazelbuild/bazel/issues/15684")
+
+ return [platform_common.ToolchainInfo(
+ py2_runtime = py2_runtime,
+ py3_runtime = py3_runtime,
+ )]
+
+# buildifier: disable=unused-variable
+def _is_py2_disabled(ctx):
+ # Because this file isn't bundled with Bazel, so we have to conditionally
+ # check for this flag.
+ # TODO: Remove this once all supported Balze versions have this flag.
+ if not hasattr(ctx.fragments.py, "disable_py"):
+ return False
+ return ctx.fragments.py.disable_py2
+
+py_runtime_pair = rule(
+ implementation = _py_runtime_pair_impl,
+ attrs = {
+ # The two runtimes are used by the py_binary at runtime, and so need to
+ # be built for the target platform.
+ "py2_runtime": attr.label(
+ providers = [PyRuntimeInfo],
+ cfg = "target",
+ doc = """\
+The runtime to use for Python 2 targets. Must have `python_version` set to
+`PY2`.
+""",
+ ),
+ "py3_runtime": attr.label(
+ providers = [PyRuntimeInfo],
+ cfg = "target",
+ doc = """\
+The runtime to use for Python 3 targets. Must have `python_version` set to
+`PY3`.
+""",
+ ),
+ },
+ fragments = ["py"],
+ doc = """\
+A toolchain rule for Python.
+
+This wraps up to two Python runtimes, one for Python 2 and one for Python 3.
+The rule consuming this toolchain will choose which runtime is appropriate.
+Either runtime may be omitted, in which case the resulting toolchain will be
+unusable for building Python code using that version.
+
+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 = Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies: