| // Copyright 2023 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package python |
| |
| import ( |
| "github.com/bazelbuild/bazel-gazelle/config" |
| "github.com/bazelbuild/bazel-gazelle/rule" |
| "github.com/emirpasic/gods/sets/treeset" |
| godsutils "github.com/emirpasic/gods/utils" |
| "path/filepath" |
| ) |
| |
| // targetBuilder builds targets to be generated by Gazelle. |
| type targetBuilder struct { |
| kind string |
| name string |
| pythonProjectRoot string |
| bzlPackage string |
| srcs *treeset.Set |
| siblingSrcs *treeset.Set |
| deps *treeset.Set |
| resolvedDeps *treeset.Set |
| visibility *treeset.Set |
| main *string |
| imports []string |
| testonly bool |
| } |
| |
| // newTargetBuilder constructs a new targetBuilder. |
| func newTargetBuilder(kind, name, pythonProjectRoot, bzlPackage string, siblingSrcs *treeset.Set) *targetBuilder { |
| return &targetBuilder{ |
| kind: kind, |
| name: name, |
| pythonProjectRoot: pythonProjectRoot, |
| bzlPackage: bzlPackage, |
| srcs: treeset.NewWith(godsutils.StringComparator), |
| siblingSrcs: siblingSrcs, |
| deps: treeset.NewWith(moduleComparator), |
| resolvedDeps: treeset.NewWith(godsutils.StringComparator), |
| visibility: treeset.NewWith(godsutils.StringComparator), |
| } |
| } |
| |
| // addSrc adds a single src to the target. |
| func (t *targetBuilder) addSrc(src string) *targetBuilder { |
| t.srcs.Add(src) |
| return t |
| } |
| |
| // addSrcs copies all values from the provided srcs to the target. |
| func (t *targetBuilder) addSrcs(srcs *treeset.Set) *targetBuilder { |
| it := srcs.Iterator() |
| for it.Next() { |
| t.srcs.Add(it.Value().(string)) |
| } |
| return t |
| } |
| |
| // addModuleDependency adds a single module dep to the target. |
| func (t *targetBuilder) addModuleDependency(dep module) *targetBuilder { |
| fileName := dep.Name + ".py" |
| if dep.From != "" { |
| fileName = dep.From + ".py" |
| } |
| if t.siblingSrcs.Contains(fileName) && fileName != filepath.Base(dep.Filepath) { |
| // importing another module from the same package, converting to absolute imports to make |
| // dependency resolution easier |
| dep.Name = importSpecFromSrc(t.pythonProjectRoot, t.bzlPackage, fileName).Imp |
| } |
| t.deps.Add(dep) |
| return t |
| } |
| |
| // addModuleDependencies copies all values from the provided deps to the target. |
| func (t *targetBuilder) addModuleDependencies(deps *treeset.Set) *targetBuilder { |
| it := deps.Iterator() |
| for it.Next() { |
| t.addModuleDependency(it.Value().(module)) |
| } |
| return t |
| } |
| |
| // addResolvedDependency adds a single dependency the target that has already |
| // been resolved or generated. The Resolver step doesn't process it further. |
| func (t *targetBuilder) addResolvedDependency(dep string) *targetBuilder { |
| t.resolvedDeps.Add(dep) |
| return t |
| } |
| |
| // addResolvedDependencies adds multiple dependencies, that have already been |
| // resolved or generated, to the target. |
| func (t *targetBuilder) addResolvedDependencies(deps []string) *targetBuilder { |
| for _, dep := range deps { |
| t.addResolvedDependency(dep) |
| } |
| return t |
| } |
| |
| // addVisibility adds visibility labels to the target. |
| func (t *targetBuilder) addVisibility(visibility []string) *targetBuilder { |
| for _, item := range visibility { |
| t.visibility.Add(item) |
| } |
| return t |
| } |
| |
| // setMain sets the main file to the target. |
| func (t *targetBuilder) setMain(main string) *targetBuilder { |
| t.main = &main |
| return t |
| } |
| |
| // setTestonly sets the testonly attribute to true. |
| func (t *targetBuilder) setTestonly() *targetBuilder { |
| t.testonly = true |
| return t |
| } |
| |
| // generateImportsAttribute generates the imports attribute. |
| // These are a list of import directories to be added to the PYTHONPATH. In our |
| // case, the value we add is on Bazel sub-packages to be able to perform imports |
| // relative to the root project package. |
| func (t *targetBuilder) generateImportsAttribute() *targetBuilder { |
| if t.pythonProjectRoot == "" { |
| // When gazelle:python_root is not set or is at the root of the repo, we don't need |
| // to set imports, because that's the Bazel's default. |
| return t |
| } |
| p, _ := filepath.Rel(t.bzlPackage, t.pythonProjectRoot) |
| p = filepath.Clean(p) |
| if p == "." { |
| return t |
| } |
| t.imports = []string{p} |
| return t |
| } |
| |
| // build returns the assembled *rule.Rule for the target. |
| func (t *targetBuilder) build() *rule.Rule { |
| r := rule.NewRule(t.kind, t.name) |
| if !t.srcs.Empty() { |
| r.SetAttr("srcs", t.srcs.Values()) |
| } |
| if !t.visibility.Empty() { |
| r.SetAttr("visibility", t.visibility.Values()) |
| } |
| if t.main != nil { |
| r.SetAttr("main", *t.main) |
| } |
| if t.imports != nil { |
| r.SetAttr("imports", t.imports) |
| } |
| if !t.deps.Empty() { |
| r.SetPrivateAttr(config.GazelleImportsKey, t.deps) |
| } |
| if t.testonly { |
| r.SetAttr("testonly", true) |
| } |
| r.SetPrivateAttr(resolvedDepsKey, t.resolvedDeps) |
| return r |
| } |