1# Copyright 2024 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"""Tests for py_info.""" 15 16load("@rules_python_internal//:rules_python_config.bzl", "config") 17load("@rules_testing//lib:analysis_test.bzl", "analysis_test") 18load("@rules_testing//lib:test_suite.bzl", "test_suite") 19load("@rules_testing//lib:util.bzl", rt_util = "util") 20load("//python:py_info.bzl", "PyInfo") 21load("//python/private:py_info.bzl", "PyInfoBuilder") # buildifier: disable=bzl-visibility 22load("//python/private:reexports.bzl", "BuiltinPyInfo") # buildifier: disable=bzl-visibility 23load("//tests/support:py_info_subject.bzl", "py_info_subject") 24 25def _provide_py_info_impl(ctx): 26 kwargs = { 27 "direct_pyc_files": depset(ctx.files.direct_pyc_files), 28 "imports": depset(ctx.attr.imports), 29 "transitive_pyc_files": depset(ctx.files.transitive_pyc_files), 30 "transitive_sources": depset(ctx.files.transitive_sources), 31 } 32 if ctx.attr.has_py2_only_sources != -1: 33 kwargs["has_py2_only_sources"] = bool(ctx.attr.has_py2_only_sources) 34 if ctx.attr.has_py3_only_sources != -1: 35 kwargs["has_py2_only_sources"] = bool(ctx.attr.has_py2_only_sources) 36 37 providers = [] 38 if config.enable_pystar: 39 providers.append(PyInfo(**kwargs)) 40 41 # Handle Bazel 6 or if Bazel autoloading is enabled 42 if not config.enable_pystar or (BuiltinPyInfo and PyInfo != BuiltinPyInfo): 43 providers.append(BuiltinPyInfo(**{ 44 k: kwargs[k] 45 for k in ( 46 "transitive_sources", 47 "has_py2_only_sources", 48 "has_py3_only_sources", 49 "uses_shared_libraries", 50 "imports", 51 ) 52 if k in kwargs 53 })) 54 return providers 55 56provide_py_info = rule( 57 implementation = _provide_py_info_impl, 58 attrs = { 59 "direct_pyc_files": attr.label_list(allow_files = True), 60 "has_py2_only_sources": attr.int(default = -1), 61 "has_py3_only_sources": attr.int(default = -1), 62 "imports": attr.string_list(), 63 "transitive_pyc_files": attr.label_list(allow_files = True), 64 "transitive_sources": attr.label_list(allow_files = True), 65 }, 66) 67 68_tests = [] 69 70def _test_py_info_create(name): 71 rt_util.helper_target( 72 native.filegroup, 73 name = name + "_files", 74 srcs = ["trans.py", "direct.pyc", "trans.pyc"], 75 ) 76 analysis_test( 77 name = name, 78 target = name + "_files", 79 impl = _test_py_info_create_impl, 80 ) 81 82def _test_py_info_create_impl(env, target): 83 trans_py, direct_pyc, trans_pyc = target[DefaultInfo].files.to_list() 84 actual = PyInfo( 85 has_py2_only_sources = True, 86 has_py3_only_sources = True, 87 imports = depset(["import-path"]), 88 transitive_sources = depset([trans_py]), 89 uses_shared_libraries = True, 90 **(dict( 91 direct_pyc_files = depset([direct_pyc]), 92 transitive_pyc_files = depset([trans_pyc]), 93 ) if config.enable_pystar else {}) 94 ) 95 96 subject = py_info_subject(actual, meta = env.expect.meta) 97 subject.uses_shared_libraries().equals(True) 98 subject.has_py2_only_sources().equals(True) 99 subject.has_py3_only_sources().equals(True) 100 subject.transitive_sources().contains_exactly(["tests/base_rules/py_info/trans.py"]) 101 subject.imports().contains_exactly(["import-path"]) 102 if config.enable_pystar: 103 subject.direct_pyc_files().contains_exactly(["tests/base_rules/py_info/direct.pyc"]) 104 subject.transitive_pyc_files().contains_exactly(["tests/base_rules/py_info/trans.pyc"]) 105 106_tests.append(_test_py_info_create) 107 108def _test_py_info_builder(name): 109 rt_util.helper_target( 110 native.filegroup, 111 name = name + "_misc", 112 srcs = ["trans.py", "direct.pyc", "trans.pyc"], 113 ) 114 115 py_info_targets = {} 116 for n in range(1, 7): 117 py_info_name = "{}_py{}".format(name, n) 118 py_info_targets["py{}".format(n)] = py_info_name 119 rt_util.helper_target( 120 provide_py_info, 121 name = py_info_name, 122 transitive_sources = ["py{}-trans.py".format(n)], 123 direct_pyc_files = ["py{}-direct.pyc".format(n)], 124 imports = ["py{}import".format(n)], 125 transitive_pyc_files = ["py{}-trans.pyc".format(n)], 126 ) 127 analysis_test( 128 name = name, 129 impl = _test_py_info_builder_impl, 130 targets = { 131 "misc": name + "_misc", 132 } | py_info_targets, 133 ) 134 135def _test_py_info_builder_impl(env, targets): 136 trans, direct_pyc, trans_pyc = targets.misc[DefaultInfo].files.to_list() 137 builder = PyInfoBuilder() 138 builder.direct_pyc_files.add(direct_pyc) 139 builder.merge_has_py2_only_sources(True) 140 builder.merge_has_py3_only_sources(True) 141 builder.imports.add("import-path") 142 builder.transitive_pyc_files.add(trans_pyc) 143 builder.transitive_sources.add(trans) 144 builder.merge_uses_shared_libraries(True) 145 146 builder.merge_target(targets.py1) 147 builder.merge_targets([targets.py2]) 148 149 builder.merge(targets.py3[PyInfo], direct = [targets.py4[PyInfo]]) 150 builder.merge_all([targets.py5[PyInfo]], direct = [targets.py6[PyInfo]]) 151 152 def check(actual): 153 subject = py_info_subject(actual, meta = env.expect.meta) 154 155 subject.uses_shared_libraries().equals(True) 156 subject.has_py2_only_sources().equals(True) 157 subject.has_py3_only_sources().equals(True) 158 159 subject.transitive_sources().contains_exactly([ 160 "tests/base_rules/py_info/trans.py", 161 "tests/base_rules/py_info/py1-trans.py", 162 "tests/base_rules/py_info/py2-trans.py", 163 "tests/base_rules/py_info/py3-trans.py", 164 "tests/base_rules/py_info/py4-trans.py", 165 "tests/base_rules/py_info/py5-trans.py", 166 "tests/base_rules/py_info/py6-trans.py", 167 ]) 168 subject.imports().contains_exactly([ 169 "import-path", 170 "py1import", 171 "py2import", 172 "py3import", 173 "py4import", 174 "py5import", 175 "py6import", 176 ]) 177 if hasattr(actual, "direct_pyc_files"): 178 subject.direct_pyc_files().contains_exactly([ 179 "tests/base_rules/py_info/direct.pyc", 180 "tests/base_rules/py_info/py4-direct.pyc", 181 "tests/base_rules/py_info/py6-direct.pyc", 182 ]) 183 subject.transitive_pyc_files().contains_exactly([ 184 "tests/base_rules/py_info/trans.pyc", 185 "tests/base_rules/py_info/py1-trans.pyc", 186 "tests/base_rules/py_info/py2-trans.pyc", 187 "tests/base_rules/py_info/py3-trans.pyc", 188 "tests/base_rules/py_info/py4-trans.pyc", 189 "tests/base_rules/py_info/py5-trans.pyc", 190 "tests/base_rules/py_info/py6-trans.pyc", 191 ]) 192 193 check(builder.build()) 194 if BuiltinPyInfo != None: 195 check(builder.build_builtin_py_info()) 196 197 builder.set_has_py2_only_sources(False) 198 builder.set_has_py3_only_sources(False) 199 builder.set_uses_shared_libraries(False) 200 201 env.expect.that_bool(builder.get_has_py2_only_sources()).equals(False) 202 env.expect.that_bool(builder.get_has_py3_only_sources()).equals(False) 203 env.expect.that_bool(builder.get_uses_shared_libraries()).equals(False) 204 205_tests.append(_test_py_info_builder) 206 207def py_info_test_suite(name): 208 test_suite( 209 name = name, 210 tests = _tests, 211 ) 212