xref: /aosp_15_r20/external/bazelbuild-rules_python/gazelle/python/target.go (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
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