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 15"""This module provides the gazelle_python_manifest macro that contains targets 16for updating and testing the Gazelle manifest file. 17""" 18 19load("@io_bazel_rules_go//go:def.bzl", "GoSource", "go_binary", "go_test") 20 21def gazelle_python_manifest( 22 name, 23 requirements, 24 modules_mapping, 25 pip_repository_name = "", 26 pip_deps_repository_name = "", 27 manifest = ":gazelle_python.yaml", 28 use_pip_repository_aliases = False): 29 """A macro for defining the updating and testing targets for the Gazelle manifest file. 30 31 Args: 32 name: the name used as a base for the targets. 33 requirements: the target for the requirements.txt file or a list of 34 requirements files that will be concatenated before passing on to 35 the manifest generator. 36 pip_repository_name: the name of the pip_install or pip_repository target. 37 use_pip_repository_aliases: boolean flag to enable using user-friendly 38 python package aliases. 39 pip_deps_repository_name: deprecated - the old pip_install target name. 40 modules_mapping: the target for the generated modules_mapping.json file. 41 manifest: the target for the Gazelle manifest file. 42 """ 43 if pip_deps_repository_name != "": 44 # buildifier: disable=print 45 print("DEPRECATED pip_deps_repository_name in //{}:{}. Please use pip_repository_name instead.".format( 46 native.package_name(), 47 name, 48 )) 49 pip_repository_name = pip_deps_repository_name 50 51 if pip_repository_name == "": 52 # This is a temporary check while pip_deps_repository_name exists as deprecated. 53 fail("pip_repository_name must be set in //{}:{}".format(native.package_name(), name)) 54 55 update_target = "{}.update".format(name) 56 update_target_label = "//{}:{}".format(native.package_name(), update_target) 57 58 manifest_generator_hash = Label("//manifest/generate:generate_lib_sources_hash") 59 60 if type(requirements) == "list": 61 native.genrule( 62 name = name + "_requirements_gen", 63 srcs = sorted(requirements), 64 outs = [name + "_requirements.txt"], 65 cmd_bash = "cat $(SRCS) > $@", 66 cmd_bat = "type $(SRCS) > $@", 67 ) 68 requirements = name + "_requirements_gen" 69 70 update_args = [ 71 "--manifest-generator-hash", 72 "$(rootpath {})".format(manifest_generator_hash), 73 "--requirements", 74 "$(rootpath {})".format(requirements), 75 "--pip-repository-name", 76 pip_repository_name, 77 "--modules-mapping", 78 "$(rootpath {})".format(modules_mapping), 79 "--output", 80 "$(rootpath {})".format(manifest), 81 "--update-target", 82 update_target_label, 83 ] 84 85 if use_pip_repository_aliases: 86 update_args += [ 87 "--use-pip-repository-aliases", 88 "true", 89 ] 90 91 go_binary( 92 name = update_target, 93 embed = [Label("//manifest/generate:generate_lib")], 94 data = [ 95 manifest, 96 modules_mapping, 97 requirements, 98 manifest_generator_hash, 99 ], 100 args = update_args, 101 visibility = ["//visibility:private"], 102 tags = ["manual"], 103 ) 104 105 go_test( 106 name = "{}.test".format(name), 107 srcs = [Label("//manifest/test:test.go")], 108 data = [ 109 manifest, 110 requirements, 111 manifest_generator_hash, 112 ], 113 env = { 114 "_TEST_MANIFEST": "$(rootpath {})".format(manifest), 115 "_TEST_MANIFEST_GENERATOR_HASH": "$(rootpath {})".format(manifest_generator_hash), 116 "_TEST_REQUIREMENTS": "$(rootpath {})".format(requirements), 117 }, 118 rundir = ".", 119 deps = [Label("//manifest")], 120 size = "small", 121 ) 122 123 native.filegroup( 124 name = name, 125 srcs = [manifest], 126 tags = ["manual"], 127 visibility = ["//visibility:public"], 128 ) 129 130# buildifier: disable=provider-params 131AllSourcesInfo = provider(fields = {"all_srcs": "All sources collected from the target and dependencies."}) 132 133_rules_python_workspace = Label("@rules_python//:WORKSPACE") 134 135def _get_all_sources_impl(target, ctx): 136 is_rules_python = target.label.workspace_name == _rules_python_workspace.workspace_name 137 if not is_rules_python: 138 # Avoid adding third-party dependency files to the checksum of the srcs. 139 return AllSourcesInfo(all_srcs = depset()) 140 srcs = depset( 141 target[GoSource].orig_srcs, 142 transitive = [dep[AllSourcesInfo].all_srcs for dep in ctx.rule.attr.deps], 143 ) 144 return [AllSourcesInfo(all_srcs = srcs)] 145 146_get_all_sources = aspect( 147 implementation = _get_all_sources_impl, 148 attr_aspects = ["deps"], 149) 150 151def _sources_hash_impl(ctx): 152 all_srcs = ctx.attr.go_library[AllSourcesInfo].all_srcs 153 hash_file = ctx.actions.declare_file(ctx.attr.name + ".hash") 154 args = ctx.actions.args() 155 args.add(hash_file) 156 args.add_all(all_srcs) 157 ctx.actions.run( 158 outputs = [hash_file], 159 inputs = all_srcs, 160 arguments = [args], 161 executable = ctx.executable._hasher, 162 ) 163 return [DefaultInfo( 164 files = depset([hash_file]), 165 runfiles = ctx.runfiles([hash_file]), 166 )] 167 168sources_hash = rule( 169 _sources_hash_impl, 170 attrs = { 171 "go_library": attr.label( 172 aspects = [_get_all_sources], 173 providers = [GoSource], 174 ), 175 "_hasher": attr.label( 176 cfg = "exec", 177 default = Label("//manifest/hasher"), 178 executable = True, 179 ), 180 }, 181) 182