• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2023 Google Inc. 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	"path/filepath"
19	"strings"
20
21	"github.com/google/blueprint/proptools"
22
23	"android/soong/android"
24	"android/soong/bazel"
25)
26
27type bazelPythonLibraryAttributes struct {
28	Srcs         bazel.LabelListAttribute
29	Deps         bazel.LabelListAttribute
30	Imports      bazel.StringListAttribute
31	Srcs_version *string
32}
33
34type bazelPythonProtoLibraryAttributes struct {
35	Deps bazel.LabelListAttribute
36}
37
38type baseAttributes struct {
39	// TODO(b/200311466): Probably not translate b/c Bazel has no good equiv
40	//Pkg_path    bazel.StringAttribute
41	// TODO: Related to Pkg_bath and similarLy gated
42	//Is_internal bazel.BoolAttribute
43	// Combines Srcs and Exclude_srcs
44	Srcs bazel.LabelListAttribute
45	Deps bazel.LabelListAttribute
46	// Combines Data and Java_data (invariant)
47	Data    bazel.LabelListAttribute
48	Imports bazel.StringListAttribute
49}
50
51func (m *PythonLibraryModule) makeArchVariantBaseAttributes(ctx android.TopDownMutatorContext) baseAttributes {
52	var attrs baseAttributes
53	archVariantBaseProps := m.GetArchVariantProperties(ctx, &BaseProperties{})
54	for axis, configToProps := range archVariantBaseProps {
55		for config, props := range configToProps {
56			if baseProps, ok := props.(*BaseProperties); ok {
57				attrs.Srcs.SetSelectValue(axis, config,
58					android.BazelLabelForModuleSrcExcludes(ctx, baseProps.Srcs, baseProps.Exclude_srcs))
59				attrs.Deps.SetSelectValue(axis, config,
60					android.BazelLabelForModuleDeps(ctx, baseProps.Libs))
61				data := android.BazelLabelForModuleSrc(ctx, baseProps.Data)
62				data.Append(android.BazelLabelForModuleSrc(ctx, baseProps.Java_data))
63				attrs.Data.SetSelectValue(axis, config, data)
64			}
65		}
66	}
67
68	partitionedSrcs := bazel.PartitionLabelListAttribute(ctx, &attrs.Srcs, bazel.LabelPartitions{
69		"proto": android.ProtoSrcLabelPartition,
70		"py":    bazel.LabelPartition{Keep_remainder: true},
71	})
72	attrs.Srcs = partitionedSrcs["py"]
73
74	if !partitionedSrcs["proto"].IsEmpty() {
75		protoInfo, _ := android.Bp2buildProtoProperties(ctx, &m.ModuleBase, partitionedSrcs["proto"])
76		protoLabel := bazel.Label{Label: ":" + protoInfo.Name}
77
78		pyProtoLibraryName := m.Name() + "_py_proto"
79		ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
80			Rule_class:        "py_proto_library",
81			Bzl_load_location: "//build/bazel/rules/python:py_proto.bzl",
82		}, android.CommonAttributes{
83			Name: pyProtoLibraryName,
84		}, &bazelPythonProtoLibraryAttributes{
85			Deps: bazel.MakeSingleLabelListAttribute(protoLabel),
86		})
87
88		attrs.Deps.Add(bazel.MakeLabelAttribute(":" + pyProtoLibraryName))
89	}
90
91	// Bazel normally requires `import path.from.top.of.tree` statements in
92	// python code, but with soong you can directly import modules from libraries.
93	// Add "imports" attributes to the bazel library so it matches soong's behavior.
94	imports := "."
95	if m.properties.Pkg_path != nil {
96		// TODO(b/215119317) This is a hack to handle the fact that we don't convert
97		// pkg_path properly right now. If the folder structure that contains this
98		// Android.bp file matches pkg_path, we can set imports to an appropriate
99		// number of ../..s to emulate moving the files under a pkg_path folder.
100		pkg_path := filepath.Clean(*m.properties.Pkg_path)
101		if strings.HasPrefix(pkg_path, "/") {
102			ctx.ModuleErrorf("pkg_path cannot start with a /: %s", pkg_path)
103		}
104
105		if !strings.HasSuffix(ctx.ModuleDir(), "/"+pkg_path) && ctx.ModuleDir() != pkg_path {
106			ctx.ModuleErrorf("Currently, bp2build only supports pkg_paths that are the same as the folders the Android.bp file is in. pkg_path: %s, module directory: %s", pkg_path, ctx.ModuleDir())
107		}
108		numFolders := strings.Count(pkg_path, "/") + 1
109		dots := make([]string, numFolders)
110		for i := 0; i < numFolders; i++ {
111			dots[i] = ".."
112		}
113		imports = strings.Join(dots, "/")
114	}
115	attrs.Imports = bazel.MakeStringListAttribute([]string{imports})
116
117	return attrs
118}
119
120func (m *PythonLibraryModule) bp2buildPythonVersion(ctx android.TopDownMutatorContext) *string {
121	py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, true)
122	py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
123	if py2Enabled && !py3Enabled {
124		return &pyVersion2
125	} else if !py2Enabled && py3Enabled {
126		return &pyVersion3
127	} else if !py2Enabled && !py3Enabled {
128		ctx.ModuleErrorf("bp2build converter doesn't understand having neither py2 nor py3 enabled")
129		return &pyVersion3
130	} else {
131		return &pyVersion2And3
132	}
133}
134
135type bazelPythonBinaryAttributes struct {
136	Main           *bazel.Label
137	Srcs           bazel.LabelListAttribute
138	Deps           bazel.LabelListAttribute
139	Python_version *string
140	Imports        bazel.StringListAttribute
141}
142
143func (p *PythonLibraryModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
144	// TODO(b/182306917): this doesn't fully handle all nested props versioned
145	// by the python version, which would have been handled by the version split
146	// mutator. This is sufficient for very simple python_library modules under
147	// Bionic.
148	baseAttrs := p.makeArchVariantBaseAttributes(ctx)
149	pyVersion := p.bp2buildPythonVersion(ctx)
150	if *pyVersion == pyVersion2And3 {
151		// Libraries default to python 2 and 3
152		pyVersion = nil
153	}
154
155	attrs := &bazelPythonLibraryAttributes{
156		Srcs:         baseAttrs.Srcs,
157		Deps:         baseAttrs.Deps,
158		Srcs_version: pyVersion,
159		Imports:      baseAttrs.Imports,
160	}
161
162	props := bazel.BazelTargetModuleProperties{
163		// Use the native py_library rule.
164		Rule_class: "py_library",
165	}
166
167	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
168		Name: p.Name(),
169		Data: baseAttrs.Data,
170	}, attrs)
171}
172
173func (p *PythonBinaryModule) bp2buildBinaryProperties(ctx android.TopDownMutatorContext) (*bazelPythonBinaryAttributes, bazel.LabelListAttribute) {
174	// TODO(b/182306917): this doesn't fully handle all nested props versioned
175	// by the python version, which would have been handled by the version split
176	// mutator. This is sufficient for very simple python_binary_host modules
177	// under Bionic.
178
179	baseAttrs := p.makeArchVariantBaseAttributes(ctx)
180	pyVersion := p.bp2buildPythonVersion(ctx)
181	if *pyVersion == pyVersion3 {
182		// Binaries default to python 3
183		pyVersion = nil
184	} else if *pyVersion == pyVersion2And3 {
185		ctx.ModuleErrorf("error for '%s' module: bp2build's python_binary_host converter "+
186			"does not support converting a module that is enabled for both Python 2 and 3 at the "+
187			"same time.", p.Name())
188	}
189
190	attrs := &bazelPythonBinaryAttributes{
191		Main:           nil,
192		Srcs:           baseAttrs.Srcs,
193		Deps:           baseAttrs.Deps,
194		Python_version: pyVersion,
195		Imports:        baseAttrs.Imports,
196	}
197
198	// main is optional.
199	if p.binaryProperties.Main != nil {
200		main := android.BazelLabelForModuleSrcSingle(ctx, *p.binaryProperties.Main)
201		attrs.Main = &main
202	}
203	return attrs, baseAttrs.Data
204}
205
206func (p *PythonBinaryModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
207	attrs, data := p.bp2buildBinaryProperties(ctx)
208
209	props := bazel.BazelTargetModuleProperties{
210		// Use the native py_binary rule.
211		Rule_class: "py_binary",
212	}
213
214	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
215		Name: p.Name(),
216		Data: data,
217	}, attrs)
218}
219
220func (p *PythonTestModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
221	// Python tests are currently exactly the same as binaries, but with a different module type
222	attrs, data := p.bp2buildBinaryProperties(ctx)
223
224	props := bazel.BazelTargetModuleProperties{
225		// Use the native py_binary rule.
226		Rule_class: "py_test",
227	}
228
229	ctx.CreateBazelTargetModule(props, android.CommonAttributes{
230		Name: p.Name(),
231		Data: data,
232	}, attrs)
233}
234