• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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