platform_build_soong/python/python.go
Colin Cross 8a49795df1 Replace ctx.ExpandSources with android.PathsForModuleSrc
Move the logic from ctx.ExpandSources into android.PathsForModuleSrc
and ctx.ExpandSource into android.PathForModuleSrc, and deprecate
them.  When combined with the pathDepsMutator this will let all
properties that take source paths also take filegroups or genrule
outputs, as long as they are tagged with `android:"path"`.

Test: All soong tests
Change-Id: I01625e76b5da19240e9649bf26a014eeeafcab8f
2019-03-20 19:36:13 +00:00

657 lines
20 KiB
Go

// Copyright 2017 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
// This file contains the "Base" module type for building Python program.
import (
"fmt"
"path/filepath"
"regexp"
"sort"
"strings"
"github.com/google/blueprint"
"github.com/google/blueprint/proptools"
"android/soong/android"
)
func init() {
android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
})
}
// the version properties that apply to python libraries and binaries.
type VersionProperties struct {
// true, if the module is required to be built with this version.
Enabled *bool `android:"arch_variant"`
// non-empty list of .py files under this strict Python version.
// srcs may reference the outputs of other modules that produce source files like genrule
// or filegroup using the syntax ":module".
Srcs []string `android:"path,arch_variant"`
// list of source files that should not be used to build the Python module.
// This is most useful in the arch/multilib variants to remove non-common files
Exclude_srcs []string `android:"path,arch_variant"`
// list of the Python libraries under this Python version.
Libs []string `android:"arch_variant"`
// true, if the binary is required to be built with embedded launcher.
// TODO(nanzhang): Remove this flag when embedded Python3 is supported later.
Embedded_launcher *bool `android:"arch_variant"`
}
// properties that apply to python libraries and binaries.
type BaseProperties struct {
// the package path prefix within the output artifact at which to place the source/data
// files of the current module.
// eg. Pkg_path = "a/b/c"; Other packages can reference this module by using
// (from a.b.c import ...) statement.
// if left unspecified, all the source/data files path is unchanged within zip file.
Pkg_path *string `android:"arch_variant"`
// true, if the Python module is used internally, eg, Python std libs.
Is_internal *bool `android:"arch_variant"`
// list of source (.py) files compatible both with Python2 and Python3 used to compile the
// Python module.
// srcs may reference the outputs of other modules that produce source files like genrule
// or filegroup using the syntax ":module".
// Srcs has to be non-empty.
Srcs []string `android:"path,arch_variant"`
// list of source files that should not be used to build the C/C++ module.
// This is most useful in the arch/multilib variants to remove non-common files
Exclude_srcs []string `android:"path,arch_variant"`
// list of files or filegroup modules that provide data that should be installed alongside
// the test. the file extension can be arbitrary except for (.py).
Data []string `android:"path,arch_variant"`
// list of the Python libraries compatible both with Python2 and Python3.
Libs []string `android:"arch_variant"`
Version struct {
// all the "srcs" or Python dependencies that are to be used only for Python2.
Py2 VersionProperties `android:"arch_variant"`
// all the "srcs" or Python dependencies that are to be used only for Python3.
Py3 VersionProperties `android:"arch_variant"`
} `android:"arch_variant"`
// the actual version each module uses after variations created.
// this property name is hidden from users' perspectives, and soong will populate it during
// runtime.
Actual_version string `blueprint:"mutated"`
}
type pathMapping struct {
dest string
src android.Path
}
type Module struct {
android.ModuleBase
android.DefaultableModuleBase
properties BaseProperties
protoProperties android.ProtoProperties
// initialize before calling Init
hod android.HostOrDeviceSupported
multilib android.Multilib
// the bootstrapper is used to bootstrap .par executable.
// bootstrapper might be nil (Python library module).
bootstrapper bootstrapper
// the installer might be nil.
installer installer
// the Python files of current module after expanding source dependencies.
// pathMapping: <dest: runfile_path, src: source_path>
srcsPathMappings []pathMapping
// the data files of current module after expanding source dependencies.
// pathMapping: <dest: runfile_path, src: source_path>
dataPathMappings []pathMapping
// the zip filepath for zipping current module source/data files.
srcsZip android.Path
// dependency modules' zip filepath for zipping current module source/data files.
depsSrcsZips android.Paths
// (.intermediate) module output path as installation source.
installSource android.OptionalPath
subAndroidMkOnce map[subAndroidMkProvider]bool
}
func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module {
return &Module{
hod: hod,
multilib: multilib,
}
}
type bootstrapper interface {
bootstrapperProps() []interface{}
bootstrap(ctx android.ModuleContext, ActualVersion string, embeddedLauncher bool,
srcsPathMappings []pathMapping, srcsZip android.Path,
depsSrcsZips android.Paths) android.OptionalPath
autorun() bool
}
type installer interface {
install(ctx android.ModuleContext, path android.Path)
setAndroidMkSharedLibs(sharedLibs []string)
}
type PythonDependency interface {
GetSrcsPathMappings() []pathMapping
GetDataPathMappings() []pathMapping
GetSrcsZip() android.Path
}
func (p *Module) GetSrcsPathMappings() []pathMapping {
return p.srcsPathMappings
}
func (p *Module) GetDataPathMappings() []pathMapping {
return p.dataPathMappings
}
func (p *Module) GetSrcsZip() android.Path {
return p.srcsZip
}
var _ PythonDependency = (*Module)(nil)
var _ android.AndroidMkDataProvider = (*Module)(nil)
func (p *Module) Init() android.Module {
p.AddProperties(&p.properties, &p.protoProperties)
if p.bootstrapper != nil {
p.AddProperties(p.bootstrapper.bootstrapperProps()...)
}
android.InitAndroidArchModule(p, p.hod, p.multilib)
android.InitDefaultableModule(p)
return p
}
type dependencyTag struct {
blueprint.BaseDependencyTag
name string
}
var (
pythonLibTag = dependencyTag{name: "pythonLib"}
launcherTag = dependencyTag{name: "launcher"}
launcherSharedLibTag = dependencyTag{name: "launcherSharedLib"}
pyIdentifierRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
pyExt = ".py"
protoExt = ".proto"
pyVersion2 = "PY2"
pyVersion3 = "PY3"
initFileName = "__init__.py"
mainFileName = "__main__.py"
entryPointFile = "entry_point.txt"
parFileExt = ".zip"
internal = "internal"
)
// create version variants for modules.
func versionSplitMutator() func(android.BottomUpMutatorContext) {
return func(mctx android.BottomUpMutatorContext) {
if base, ok := mctx.Module().(*Module); ok {
versionNames := []string{}
if base.properties.Version.Py2.Enabled != nil &&
*(base.properties.Version.Py2.Enabled) == true {
versionNames = append(versionNames, pyVersion2)
}
if !(base.properties.Version.Py3.Enabled != nil &&
*(base.properties.Version.Py3.Enabled) == false) {
versionNames = append(versionNames, pyVersion3)
}
modules := mctx.CreateVariations(versionNames...)
for i, v := range versionNames {
// set the actual version for Python module.
modules[i].(*Module).properties.Actual_version = v
}
}
}
}
func (p *Module) HostToolPath() android.OptionalPath {
if p.installer == nil {
// python_library is just meta module, and doesn't have any installer.
return android.OptionalPath{}
}
return android.OptionalPathForPath(p.installer.(*binaryDecorator).path)
}
func (p *Module) isEmbeddedLauncherEnabled(actual_version string) bool {
switch actual_version {
case pyVersion2:
return Bool(p.properties.Version.Py2.Embedded_launcher)
case pyVersion3:
return Bool(p.properties.Version.Py3.Embedded_launcher)
}
return false
}
func hasSrcExt(srcs []string, ext string) bool {
for _, src := range srcs {
if filepath.Ext(src) == ext {
return true
}
}
return false
}
func (p *Module) hasSrcExt(ctx android.BottomUpMutatorContext, ext string) bool {
if hasSrcExt(p.properties.Srcs, protoExt) {
return true
}
switch p.properties.Actual_version {
case pyVersion2:
return hasSrcExt(p.properties.Version.Py2.Srcs, protoExt)
case pyVersion3:
return hasSrcExt(p.properties.Version.Py3.Srcs, protoExt)
default:
panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
p.properties.Actual_version, ctx.ModuleName()))
}
}
func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) {
if p.hasSrcExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
ctx.AddVariationDependencies(nil, pythonLibTag, "libprotobuf-python")
}
switch p.properties.Actual_version {
case pyVersion2:
ctx.AddVariationDependencies(nil, pythonLibTag,
uniqueLibs(ctx, p.properties.Libs, "version.py2.libs",
p.properties.Version.Py2.Libs)...)
if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled(pyVersion2) {
ctx.AddVariationDependencies(nil, pythonLibTag, "py2-stdlib")
launcherModule := "py2-launcher"
if p.bootstrapper.autorun() {
launcherModule = "py2-launcher-autorun"
}
ctx.AddFarVariationDependencies([]blueprint.Variation{
{Mutator: "arch", Variation: ctx.Target().String()},
}, launcherTag, launcherModule)
// Add py2-launcher shared lib dependencies. Ideally, these should be
// derived from the `shared_libs` property of "py2-launcher". However, we
// cannot read the property at this stage and it will be too late to add
// dependencies later.
ctx.AddFarVariationDependencies([]blueprint.Variation{
{Mutator: "arch", Variation: ctx.Target().String()},
}, launcherSharedLibTag, "libsqlite")
if ctx.Target().Os.Bionic() {
ctx.AddFarVariationDependencies([]blueprint.Variation{
{Mutator: "arch", Variation: ctx.Target().String()},
}, launcherSharedLibTag, "libc", "libdl", "libm")
}
}
case pyVersion3:
ctx.AddVariationDependencies(nil, pythonLibTag,
uniqueLibs(ctx, p.properties.Libs, "version.py3.libs",
p.properties.Version.Py3.Libs)...)
if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled(pyVersion3) {
//TODO(nanzhang): Add embedded launcher for Python3.
ctx.PropertyErrorf("version.py3.embedded_launcher",
"is not supported yet for Python3.")
}
default:
panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
p.properties.Actual_version, ctx.ModuleName()))
}
}
// check "libs" duplicates from current module dependencies.
func uniqueLibs(ctx android.BottomUpMutatorContext,
commonLibs []string, versionProp string, versionLibs []string) []string {
set := make(map[string]string)
ret := []string{}
// deps from "libs" property.
for _, l := range commonLibs {
if _, found := set[l]; found {
ctx.PropertyErrorf("libs", "%q has duplicates within libs.", l)
} else {
set[l] = "libs"
ret = append(ret, l)
}
}
// deps from "version.pyX.libs" property.
for _, l := range versionLibs {
if _, found := set[l]; found {
ctx.PropertyErrorf(versionProp, "%q has duplicates within %q.", set[l])
} else {
set[l] = versionProp
ret = append(ret, l)
}
}
return ret
}
func (p *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) {
p.GeneratePythonBuildActions(ctx)
// Only Python binaries and test has non-empty bootstrapper.
if p.bootstrapper != nil {
p.walkTransitiveDeps(ctx)
// TODO(nanzhang): Since embedded launcher is not supported for Python3 for now,
// so we initialize "embedded_launcher" to false.
embeddedLauncher := false
if p.properties.Actual_version == pyVersion2 {
embeddedLauncher = p.isEmbeddedLauncherEnabled(pyVersion2)
}
p.installSource = p.bootstrapper.bootstrap(ctx, p.properties.Actual_version,
embeddedLauncher, p.srcsPathMappings, p.srcsZip, p.depsSrcsZips)
}
if p.installer != nil {
var sharedLibs []string
ctx.VisitDirectDeps(func(dep android.Module) {
if ctx.OtherModuleDependencyTag(dep) == launcherSharedLibTag {
sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep))
}
})
p.installer.setAndroidMkSharedLibs(sharedLibs)
if p.installSource.Valid() {
p.installer.install(ctx, p.installSource.Path())
}
}
}
func (p *Module) GeneratePythonBuildActions(ctx android.ModuleContext) {
// expand python files from "srcs" property.
srcs := p.properties.Srcs
exclude_srcs := p.properties.Exclude_srcs
switch p.properties.Actual_version {
case pyVersion2:
srcs = append(srcs, p.properties.Version.Py2.Srcs...)
exclude_srcs = append(exclude_srcs, p.properties.Version.Py2.Exclude_srcs...)
case pyVersion3:
srcs = append(srcs, p.properties.Version.Py3.Srcs...)
exclude_srcs = append(exclude_srcs, p.properties.Version.Py3.Exclude_srcs...)
default:
panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
p.properties.Actual_version, ctx.ModuleName()))
}
expandedSrcs := android.PathsForModuleSrcExcludes(ctx, srcs, exclude_srcs)
requiresSrcs := true
if p.bootstrapper != nil && !p.bootstrapper.autorun() {
requiresSrcs = false
}
if len(expandedSrcs) == 0 && requiresSrcs {
ctx.ModuleErrorf("doesn't have any source files!")
}
// expand data files from "data" property.
expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
// sanitize pkg_path.
pkgPath := String(p.properties.Pkg_path)
if pkgPath != "" {
pkgPath = filepath.Clean(String(p.properties.Pkg_path))
if pkgPath == ".." || strings.HasPrefix(pkgPath, "../") ||
strings.HasPrefix(pkgPath, "/") {
ctx.PropertyErrorf("pkg_path",
"%q must be a relative path contained in par file.",
String(p.properties.Pkg_path))
return
}
if p.properties.Is_internal != nil && *p.properties.Is_internal {
pkgPath = filepath.Join(internal, pkgPath)
}
} else {
if p.properties.Is_internal != nil && *p.properties.Is_internal {
pkgPath = internal
}
}
p.genModulePathMappings(ctx, pkgPath, expandedSrcs, expandedData)
p.srcsZip = p.createSrcsZip(ctx, pkgPath)
}
// generate current module unique pathMappings: <dest: runfiles_path, src: source_path>
// for python/data files.
func (p *Module) genModulePathMappings(ctx android.ModuleContext, pkgPath string,
expandedSrcs, expandedData android.Paths) {
// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
// check current module duplicates.
destToPySrcs := make(map[string]string)
destToPyData := make(map[string]string)
for _, s := range expandedSrcs {
if s.Ext() != pyExt && s.Ext() != protoExt {
ctx.PropertyErrorf("srcs", "found non (.py|.proto) file: %q!", s.String())
continue
}
runfilesPath := filepath.Join(pkgPath, s.Rel())
identifiers := strings.Split(strings.TrimSuffix(runfilesPath,
filepath.Ext(runfilesPath)), "/")
for _, token := range identifiers {
if !pyIdentifierRegexp.MatchString(token) {
ctx.PropertyErrorf("srcs", "the path %q contains invalid token %q.",
runfilesPath, token)
}
}
if fillInMap(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) {
p.srcsPathMappings = append(p.srcsPathMappings,
pathMapping{dest: runfilesPath, src: s})
}
}
for _, d := range expandedData {
if d.Ext() == pyExt || d.Ext() == protoExt {
ctx.PropertyErrorf("data", "found (.py|.proto) file: %q!", d.String())
continue
}
runfilesPath := filepath.Join(pkgPath, d.Rel())
if fillInMap(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) {
p.dataPathMappings = append(p.dataPathMappings,
pathMapping{dest: runfilesPath, src: d})
}
}
}
// register build actions to zip current module's sources.
func (p *Module) createSrcsZip(ctx android.ModuleContext, pkgPath string) android.Path {
relativeRootMap := make(map[string]android.Paths)
pathMappings := append(p.srcsPathMappings, p.dataPathMappings...)
var protoSrcs android.Paths
// "srcs" or "data" properties may have filegroup so it might happen that
// the relative root for each source path is different.
for _, path := range pathMappings {
if path.src.Ext() == protoExt {
protoSrcs = append(protoSrcs, path.src)
} else {
var relativeRoot string
relativeRoot = strings.TrimSuffix(path.src.String(), path.src.Rel())
if v, found := relativeRootMap[relativeRoot]; found {
relativeRootMap[relativeRoot] = append(v, path.src)
} else {
relativeRootMap[relativeRoot] = android.Paths{path.src}
}
}
}
var zips android.Paths
if len(protoSrcs) > 0 {
for _, srcFile := range protoSrcs {
zip := genProto(ctx, &p.protoProperties, srcFile,
android.ProtoFlags(ctx, &p.protoProperties), pkgPath)
zips = append(zips, zip)
}
}
if len(relativeRootMap) > 0 {
var keys []string
// in order to keep stable order of soong_zip params, we sort the keys here.
for k := range relativeRootMap {
keys = append(keys, k)
}
sort.Strings(keys)
parArgs := []string{}
if pkgPath != "" {
parArgs = append(parArgs, `-P `+pkgPath)
}
implicits := android.Paths{}
for _, k := range keys {
parArgs = append(parArgs, `-C `+k)
for _, path := range relativeRootMap[k] {
parArgs = append(parArgs, `-f `+path.String())
implicits = append(implicits, path)
}
}
origSrcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".py.srcszip")
ctx.Build(pctx, android.BuildParams{
Rule: zip,
Description: "python library archive",
Output: origSrcsZip,
Implicits: implicits,
Args: map[string]string{
"args": strings.Join(parArgs, " "),
},
})
zips = append(zips, origSrcsZip)
}
if len(zips) == 1 {
return zips[0]
} else {
combinedSrcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".srcszip")
ctx.Build(pctx, android.BuildParams{
Rule: combineZip,
Description: "combine python library archive",
Output: combinedSrcsZip,
Inputs: zips,
})
return combinedSrcsZip
}
}
func isPythonLibModule(module blueprint.Module) bool {
if m, ok := module.(*Module); ok {
// Python library has no bootstrapper or installer.
if m.bootstrapper != nil || m.installer != nil {
return false
}
return true
}
return false
}
// check Python source/data files duplicates for whole runfiles tree since Python binary/test
// need collect and zip all srcs of whole transitive dependencies to a final par file.
func (p *Module) walkTransitiveDeps(ctx android.ModuleContext) {
// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
// check duplicates.
destToPySrcs := make(map[string]string)
destToPyData := make(map[string]string)
for _, path := range p.srcsPathMappings {
destToPySrcs[path.dest] = path.src.String()
}
for _, path := range p.dataPathMappings {
destToPyData[path.dest] = path.src.String()
}
seen := make(map[android.Module]bool)
// visit all its dependencies in depth first.
ctx.WalkDeps(func(child, parent android.Module) bool {
if ctx.OtherModuleDependencyTag(child) != pythonLibTag {
return false
}
if seen[child] {
return false
}
seen[child] = true
// Python modules only can depend on Python libraries.
if !isPythonLibModule(child) {
panic(fmt.Errorf(
"the dependency %q of module %q is not Python library!",
ctx.ModuleName(), ctx.OtherModuleName(child)))
}
if dep, ok := child.(PythonDependency); ok {
srcs := dep.GetSrcsPathMappings()
for _, path := range srcs {
if !fillInMap(ctx, destToPySrcs,
path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child)) {
continue
}
}
data := dep.GetDataPathMappings()
for _, path := range data {
fillInMap(ctx, destToPyData,
path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
}
p.depsSrcsZips = append(p.depsSrcsZips, dep.GetSrcsZip())
}
return true
})
}
func fillInMap(ctx android.ModuleContext, m map[string]string,
key, value, curModule, otherModule string) bool {
if oldValue, found := m[key]; found {
ctx.ModuleErrorf("found two files to be placed at the same location within zip %q."+
" First file: in module %s at path %q."+
" Second file: in module %s at path %q.",
key, curModule, oldValue, otherModule, value)
return false
} else {
m[key] = value
}
return true
}
func (p *Module) InstallInData() bool {
return true
}
var Bool = proptools.Bool
var BoolDefault = proptools.BoolDefault
var String = proptools.String