// 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 }