| # Copyright 2023 The Bazel Authors. All rights reserved. | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| "Implementation of py_package rule" | |
| load(":builders.bzl", "builders") | |
| load(":py_info.bzl", "PyInfoBuilder") | |
| def _path_inside_wheel(input_file): | |
| # input_file.short_path is sometimes relative ("../${repository_root}/foobar") | |
| # which is not a valid path within a zip file. Fix that. | |
| short_path = input_file.short_path | |
| if short_path.startswith("..") and len(short_path) >= 3: | |
| # Path separator. '/' on linux. | |
| separator = short_path[2] | |
| # Consume '../' part. | |
| short_path = short_path[3:] | |
| # Find position of next '/' and consume everything up to that character. | |
| pos = short_path.find(separator) | |
| short_path = short_path[pos + 1:] | |
| return short_path | |
| def _py_package_impl(ctx): | |
| inputs = builders.DepsetBuilder() | |
| py_info = PyInfoBuilder.new() | |
| for dep in ctx.attr.deps: | |
| inputs.add(dep[DefaultInfo].data_runfiles.files) | |
| inputs.add(dep[DefaultInfo].default_runfiles.files) | |
| py_info.merge_target(dep) | |
| py_info = py_info.build() | |
| inputs.add(py_info.transitive_sources) | |
| inputs.add(py_info.transitive_pyc_files) | |
| inputs.add(py_info.transitive_pyi_files) | |
| inputs = inputs.build() | |
| # TODO: '/' is wrong on windows, but the path separator is not available in starlark. | |
| # Fix this once ctx.configuration has directory separator information. | |
| packages = [p.replace(".", "/") for p in ctx.attr.packages] | |
| if not packages: | |
| filtered_inputs = inputs | |
| else: | |
| filtered_files = [] | |
| # TODO: flattening depset to list gives poor performance, | |
| for input_file in inputs.to_list(): | |
| wheel_path = _path_inside_wheel(input_file) | |
| for package in packages: | |
| if wheel_path.startswith(package): | |
| filtered_files.append(input_file) | |
| filtered_inputs = depset(direct = filtered_files) | |
| return [DefaultInfo( | |
| files = filtered_inputs, | |
| )] | |
| py_package_lib = struct( | |
| implementation = _py_package_impl, | |
| attrs = { | |
| "deps": attr.label_list( | |
| doc = "", | |
| ), | |
| "packages": attr.string_list( | |
| mandatory = False, | |
| allow_empty = True, | |
| doc = """\ | |
| List of Python packages to include in the distribution. | |
| Sub-packages are automatically included. | |
| """, | |
| ), | |
| }, | |
| path_inside_wheel = _path_inside_wheel, | |
| ) |