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 15package python 16 17import ( 18 "github.com/bazelbuild/bazel-gazelle/config" 19 "github.com/bazelbuild/bazel-gazelle/rule" 20 "github.com/emirpasic/gods/sets/treeset" 21 godsutils "github.com/emirpasic/gods/utils" 22 "path/filepath" 23) 24 25// targetBuilder builds targets to be generated by Gazelle. 26type targetBuilder struct { 27 kind string 28 name string 29 pythonProjectRoot string 30 bzlPackage string 31 srcs *treeset.Set 32 siblingSrcs *treeset.Set 33 deps *treeset.Set 34 resolvedDeps *treeset.Set 35 visibility *treeset.Set 36 main *string 37 imports []string 38 testonly bool 39} 40 41// newTargetBuilder constructs a new targetBuilder. 42func newTargetBuilder(kind, name, pythonProjectRoot, bzlPackage string, siblingSrcs *treeset.Set) *targetBuilder { 43 return &targetBuilder{ 44 kind: kind, 45 name: name, 46 pythonProjectRoot: pythonProjectRoot, 47 bzlPackage: bzlPackage, 48 srcs: treeset.NewWith(godsutils.StringComparator), 49 siblingSrcs: siblingSrcs, 50 deps: treeset.NewWith(moduleComparator), 51 resolvedDeps: treeset.NewWith(godsutils.StringComparator), 52 visibility: treeset.NewWith(godsutils.StringComparator), 53 } 54} 55 56// addSrc adds a single src to the target. 57func (t *targetBuilder) addSrc(src string) *targetBuilder { 58 t.srcs.Add(src) 59 return t 60} 61 62// addSrcs copies all values from the provided srcs to the target. 63func (t *targetBuilder) addSrcs(srcs *treeset.Set) *targetBuilder { 64 it := srcs.Iterator() 65 for it.Next() { 66 t.srcs.Add(it.Value().(string)) 67 } 68 return t 69} 70 71// addModuleDependency adds a single module dep to the target. 72func (t *targetBuilder) addModuleDependency(dep module) *targetBuilder { 73 fileName := dep.Name + ".py" 74 if dep.From != "" { 75 fileName = dep.From + ".py" 76 } 77 if t.siblingSrcs.Contains(fileName) && fileName != filepath.Base(dep.Filepath) { 78 // importing another module from the same package, converting to absolute imports to make 79 // dependency resolution easier 80 dep.Name = importSpecFromSrc(t.pythonProjectRoot, t.bzlPackage, fileName).Imp 81 } 82 t.deps.Add(dep) 83 return t 84} 85 86// addModuleDependencies copies all values from the provided deps to the target. 87func (t *targetBuilder) addModuleDependencies(deps *treeset.Set) *targetBuilder { 88 it := deps.Iterator() 89 for it.Next() { 90 t.addModuleDependency(it.Value().(module)) 91 } 92 return t 93} 94 95// addResolvedDependency adds a single dependency the target that has already 96// been resolved or generated. The Resolver step doesn't process it further. 97func (t *targetBuilder) addResolvedDependency(dep string) *targetBuilder { 98 t.resolvedDeps.Add(dep) 99 return t 100} 101 102// addResolvedDependencies adds multiple dependencies, that have already been 103// resolved or generated, to the target. 104func (t *targetBuilder) addResolvedDependencies(deps []string) *targetBuilder { 105 for _, dep := range deps { 106 t.addResolvedDependency(dep) 107 } 108 return t 109} 110 111// addVisibility adds visibility labels to the target. 112func (t *targetBuilder) addVisibility(visibility []string) *targetBuilder { 113 for _, item := range visibility { 114 t.visibility.Add(item) 115 } 116 return t 117} 118 119// setMain sets the main file to the target. 120func (t *targetBuilder) setMain(main string) *targetBuilder { 121 t.main = &main 122 return t 123} 124 125// setTestonly sets the testonly attribute to true. 126func (t *targetBuilder) setTestonly() *targetBuilder { 127 t.testonly = true 128 return t 129} 130 131// generateImportsAttribute generates the imports attribute. 132// These are a list of import directories to be added to the PYTHONPATH. In our 133// case, the value we add is on Bazel sub-packages to be able to perform imports 134// relative to the root project package. 135func (t *targetBuilder) generateImportsAttribute() *targetBuilder { 136 if t.pythonProjectRoot == "" { 137 // When gazelle:python_root is not set or is at the root of the repo, we don't need 138 // to set imports, because that's the Bazel's default. 139 return t 140 } 141 p, _ := filepath.Rel(t.bzlPackage, t.pythonProjectRoot) 142 p = filepath.Clean(p) 143 if p == "." { 144 return t 145 } 146 t.imports = []string{p} 147 return t 148} 149 150// build returns the assembled *rule.Rule for the target. 151func (t *targetBuilder) build() *rule.Rule { 152 r := rule.NewRule(t.kind, t.name) 153 if !t.srcs.Empty() { 154 r.SetAttr("srcs", t.srcs.Values()) 155 } 156 if !t.visibility.Empty() { 157 r.SetAttr("visibility", t.visibility.Values()) 158 } 159 if t.main != nil { 160 r.SetAttr("main", *t.main) 161 } 162 if t.imports != nil { 163 r.SetAttr("imports", t.imports) 164 } 165 if !t.deps.Empty() { 166 r.SetPrivateAttr(config.GazelleImportsKey, t.deps) 167 } 168 if t.testonly { 169 r.SetAttr("testonly", true) 170 } 171 r.SetPrivateAttr(resolvedDepsKey, t.resolvedDeps) 172 return r 173} 174