1# Copyright 2023 The Bazel Authors. All rights reserved. 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14"Implementation of py_package rule" 15 16load(":builders.bzl", "builders") 17load(":py_info.bzl", "PyInfoBuilder") 18 19def _path_inside_wheel(input_file): 20 # input_file.short_path is sometimes relative ("../${repository_root}/foobar") 21 # which is not a valid path within a zip file. Fix that. 22 short_path = input_file.short_path 23 if short_path.startswith("..") and len(short_path) >= 3: 24 # Path separator. '/' on linux. 25 separator = short_path[2] 26 27 # Consume '../' part. 28 short_path = short_path[3:] 29 30 # Find position of next '/' and consume everything up to that character. 31 pos = short_path.find(separator) 32 short_path = short_path[pos + 1:] 33 return short_path 34 35def _py_package_impl(ctx): 36 inputs = builders.DepsetBuilder() 37 py_info = PyInfoBuilder() 38 for dep in ctx.attr.deps: 39 inputs.add(dep[DefaultInfo].data_runfiles.files) 40 inputs.add(dep[DefaultInfo].default_runfiles.files) 41 py_info.merge_target(dep) 42 py_info = py_info.build() 43 inputs.add(py_info.transitive_sources) 44 45 # Remove conditional once Bazel 6 support dropped. 46 if hasattr(py_info, "transitive_pyc_files"): 47 inputs.add(py_info.transitive_pyc_files) 48 49 inputs = inputs.build() 50 51 # TODO: '/' is wrong on windows, but the path separator is not available in starlark. 52 # Fix this once ctx.configuration has directory separator information. 53 packages = [p.replace(".", "/") for p in ctx.attr.packages] 54 if not packages: 55 filtered_inputs = inputs 56 else: 57 filtered_files = [] 58 59 # TODO: flattening depset to list gives poor performance, 60 for input_file in inputs.to_list(): 61 wheel_path = _path_inside_wheel(input_file) 62 for package in packages: 63 if wheel_path.startswith(package): 64 filtered_files.append(input_file) 65 filtered_inputs = depset(direct = filtered_files) 66 67 return [DefaultInfo( 68 files = filtered_inputs, 69 )] 70 71py_package_lib = struct( 72 implementation = _py_package_impl, 73 attrs = { 74 "deps": attr.label_list( 75 doc = "", 76 ), 77 "packages": attr.string_list( 78 mandatory = False, 79 allow_empty = True, 80 doc = """\ 81List of Python packages to include in the distribution. 82Sub-packages are automatically included. 83""", 84 ), 85 }, 86 path_inside_wheel = _path_inside_wheel, 87) 88