platform_build_soong/python/bp2build.go
Cole Faust 4d247e6f21 Refactor python rules
The goal of this cl is to simplify the python rules,
mostly by removing the "decorator" pattern that they
currently use, and instead making separate module
types for libraries, binaries, and tests that inherit
from each other.

Bug: 259718110
Test: Verified ninja files are unchanged (they only change in the list of soong sources because I added/deleted files)
Change-Id: I1e836e2cc4782c7818f91db7df7895de3b8db7ca
2023-01-25 10:40:59 -08:00

226 lines
7.8 KiB
Go

// Copyright 2023 Google Inc. 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 (
"fmt"
"path/filepath"
"strings"
"github.com/google/blueprint/proptools"
"android/soong/android"
"android/soong/bazel"
)
type bazelPythonLibraryAttributes struct {
Srcs bazel.LabelListAttribute
Deps bazel.LabelListAttribute
Imports bazel.StringListAttribute
Srcs_version *string
}
type bazelPythonProtoLibraryAttributes struct {
Deps bazel.LabelListAttribute
}
type baseAttributes struct {
// TODO(b/200311466): Probably not translate b/c Bazel has no good equiv
//Pkg_path bazel.StringAttribute
// TODO: Related to Pkg_bath and similarLy gated
//Is_internal bazel.BoolAttribute
// Combines Srcs and Exclude_srcs
Srcs bazel.LabelListAttribute
Deps bazel.LabelListAttribute
// Combines Data and Java_data (invariant)
Data bazel.LabelListAttribute
Imports bazel.StringListAttribute
}
func (m *PythonLibraryModule) makeArchVariantBaseAttributes(ctx android.TopDownMutatorContext) baseAttributes {
var attrs baseAttributes
archVariantBaseProps := m.GetArchVariantProperties(ctx, &BaseProperties{})
for axis, configToProps := range archVariantBaseProps {
for config, props := range configToProps {
if baseProps, ok := props.(*BaseProperties); ok {
attrs.Srcs.SetSelectValue(axis, config,
android.BazelLabelForModuleSrcExcludes(ctx, baseProps.Srcs, baseProps.Exclude_srcs))
attrs.Deps.SetSelectValue(axis, config,
android.BazelLabelForModuleDeps(ctx, baseProps.Libs))
data := android.BazelLabelForModuleSrc(ctx, baseProps.Data)
data.Append(android.BazelLabelForModuleSrc(ctx, baseProps.Java_data))
attrs.Data.SetSelectValue(axis, config, data)
}
}
}
partitionedSrcs := bazel.PartitionLabelListAttribute(ctx, &attrs.Srcs, bazel.LabelPartitions{
"proto": android.ProtoSrcLabelPartition,
"py": bazel.LabelPartition{Keep_remainder: true},
})
attrs.Srcs = partitionedSrcs["py"]
if !partitionedSrcs["proto"].IsEmpty() {
protoInfo, _ := android.Bp2buildProtoProperties(ctx, &m.ModuleBase, partitionedSrcs["proto"])
protoLabel := bazel.Label{Label: ":" + protoInfo.Name}
pyProtoLibraryName := m.Name() + "_py_proto"
ctx.CreateBazelTargetModule(bazel.BazelTargetModuleProperties{
Rule_class: "py_proto_library",
Bzl_load_location: "//build/bazel/rules/python:py_proto.bzl",
}, android.CommonAttributes{
Name: pyProtoLibraryName,
}, &bazelPythonProtoLibraryAttributes{
Deps: bazel.MakeSingleLabelListAttribute(protoLabel),
})
attrs.Deps.Add(bazel.MakeLabelAttribute(":" + pyProtoLibraryName))
}
// Bazel normally requires `import path.from.top.of.tree` statements in
// python code, but with soong you can directly import modules from libraries.
// Add "imports" attributes to the bazel library so it matches soong's behavior.
imports := "."
if m.properties.Pkg_path != nil {
// TODO(b/215119317) This is a hack to handle the fact that we don't convert
// pkg_path properly right now. If the folder structure that contains this
// Android.bp file matches pkg_path, we can set imports to an appropriate
// number of ../..s to emulate moving the files under a pkg_path folder.
pkg_path := filepath.Clean(*m.properties.Pkg_path)
if strings.HasPrefix(pkg_path, "/") {
ctx.ModuleErrorf("pkg_path cannot start with a /: %s", pkg_path)
}
if !strings.HasSuffix(ctx.ModuleDir(), "/"+pkg_path) && ctx.ModuleDir() != pkg_path {
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())
}
numFolders := strings.Count(pkg_path, "/") + 1
dots := make([]string, numFolders)
for i := 0; i < numFolders; i++ {
dots[i] = ".."
}
imports = strings.Join(dots, "/")
}
attrs.Imports = bazel.MakeStringListAttribute([]string{imports})
return attrs
}
func pythonLibBp2Build(ctx android.TopDownMutatorContext, m *PythonLibraryModule) {
// TODO(b/182306917): this doesn't fully handle all nested props versioned
// by the python version, which would have been handled by the version split
// mutator. This is sufficient for very simple python_library modules under
// Bionic.
py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, true)
py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
var python_version *string
if py2Enabled && !py3Enabled {
python_version = &pyVersion2
} else if !py2Enabled && py3Enabled {
python_version = &pyVersion3
} else if !py2Enabled && !py3Enabled {
ctx.ModuleErrorf("bp2build converter doesn't understand having neither py2 nor py3 enabled")
} else {
// do nothing, since python_version defaults to PY2ANDPY3
}
baseAttrs := m.makeArchVariantBaseAttributes(ctx)
attrs := &bazelPythonLibraryAttributes{
Srcs: baseAttrs.Srcs,
Deps: baseAttrs.Deps,
Srcs_version: python_version,
Imports: baseAttrs.Imports,
}
props := bazel.BazelTargetModuleProperties{
// Use the native py_library rule.
Rule_class: "py_library",
}
ctx.CreateBazelTargetModule(props, android.CommonAttributes{
Name: m.Name(),
Data: baseAttrs.Data,
}, attrs)
}
type bazelPythonBinaryAttributes struct {
Main *bazel.Label
Srcs bazel.LabelListAttribute
Deps bazel.LabelListAttribute
Python_version *string
Imports bazel.StringListAttribute
}
func pythonBinaryBp2Build(ctx android.TopDownMutatorContext, m *PythonBinaryModule) {
// TODO(b/182306917): this doesn't fully handle all nested props versioned
// by the python version, which would have been handled by the version split
// mutator. This is sufficient for very simple python_binary_host modules
// under Bionic.
py3Enabled := proptools.BoolDefault(m.properties.Version.Py3.Enabled, false)
py2Enabled := proptools.BoolDefault(m.properties.Version.Py2.Enabled, false)
var python_version *string
if py3Enabled && py2Enabled {
panic(fmt.Errorf(
"error for '%s' module: bp2build's python_binary_host converter does not support "+
"converting a module that is enabled for both Python 2 and 3 at the same time.", m.Name()))
} else if py2Enabled {
python_version = &pyVersion2
} else {
// do nothing, since python_version defaults to PY3.
}
baseAttrs := m.makeArchVariantBaseAttributes(ctx)
attrs := &bazelPythonBinaryAttributes{
Main: nil,
Srcs: baseAttrs.Srcs,
Deps: baseAttrs.Deps,
Python_version: python_version,
Imports: baseAttrs.Imports,
}
for _, propIntf := range m.GetProperties() {
if props, ok := propIntf.(*BinaryProperties); ok {
// main is optional.
if props.Main != nil {
main := android.BazelLabelForModuleSrcSingle(ctx, *props.Main)
attrs.Main = &main
break
}
}
}
props := bazel.BazelTargetModuleProperties{
// Use the native py_binary rule.
Rule_class: "py_binary",
}
ctx.CreateBazelTargetModule(props, android.CommonAttributes{
Name: m.Name(),
Data: baseAttrs.Data,
}, attrs)
}
func (p *PythonLibraryModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
pythonLibBp2Build(ctx, p)
}
func (p *PythonBinaryModule) ConvertWithBp2build(ctx android.TopDownMutatorContext) {
pythonBinaryBp2Build(ctx, p)
}
func (p *PythonTestModule) ConvertWithBp2build(_ android.TopDownMutatorContext) {
// Tests are currently unsupported
}