339a63f044
Due to upstream changes in python path calculations, I'm simplifying the python launcher to work more like a standard python distribution. This is effectively changing the stdlib path from `internal/stdlib` to `internal/python3.10` (or `3.11`, etc). This allows us to specify the zip file as PYTHONHOME, set PYTHONPLATLIBDIR to `internal` and use the default detection after that. That does mean during upgrades that the stdlib pkg path will change, so move the source vs prebuilt calculation from the Android.bp into Soong to choose which stdlib module to pick up (with the corresponding `pkg_path`) Bug: 278602456 Test: treehugger with python3.10 Test: a python3.11 source + 3.10 prebuilt build Test: a python3.11 source+prebuilt build Change-Id: I8b02e7b22a1f1d1e02819ae1a31a99cdc985542c
766 lines
28 KiB
Go
766 lines
28 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"
|
|
"strings"
|
|
|
|
"github.com/google/blueprint"
|
|
"github.com/google/blueprint/proptools"
|
|
|
|
"android/soong/android"
|
|
)
|
|
|
|
func init() {
|
|
registerPythonMutators(android.InitRegistrationContext)
|
|
}
|
|
|
|
func registerPythonMutators(ctx android.RegistrationContext) {
|
|
ctx.PreDepsMutators(RegisterPythonPreDepsMutators)
|
|
}
|
|
|
|
// Exported to support other packages using Python modules in tests.
|
|
func RegisterPythonPreDepsMutators(ctx android.RegisterMutatorsContext) {
|
|
ctx.BottomUp("python_version", versionSplitMutator()).Parallel()
|
|
}
|
|
|
|
// the version-specific properties that apply to python modules.
|
|
type VersionProperties struct {
|
|
// whether the module is required to be built with this version.
|
|
// Defaults to true for Python 3, and false otherwise.
|
|
Enabled *bool
|
|
|
|
// list of source files specific to this Python version.
|
|
// Using the syntax ":module", srcs may reference the outputs of other modules that produce source files,
|
|
// e.g. genrule or filegroup.
|
|
Srcs []string `android:"path,arch_variant"`
|
|
|
|
// list of source files that should not be used to build the Python module for this version.
|
|
// This is most useful to remove files that are not common to all Python versions.
|
|
Exclude_srcs []string `android:"path,arch_variant"`
|
|
|
|
// list of the Python libraries used only for this Python version.
|
|
Libs []string `android:"arch_variant"`
|
|
|
|
// whether the binary is required to be built with embedded launcher for this version, defaults to false.
|
|
Embedded_launcher *bool // TODO(b/174041232): Remove this property
|
|
}
|
|
|
|
// properties that apply to all python modules
|
|
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
|
|
|
|
// true, if the Python module is used internally, eg, Python std libs.
|
|
Is_internal *bool
|
|
|
|
// 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 java modules that provide data that should be installed alongside the test.
|
|
Java_data []string
|
|
|
|
// list of the Python libraries compatible both with Python2 and Python3.
|
|
Libs []string `android:"arch_variant"`
|
|
|
|
Version struct {
|
|
// Python2-specific properties, including whether Python2 is supported for this module
|
|
// and version-specific sources, exclusions and dependencies.
|
|
Py2 VersionProperties `android:"arch_variant"`
|
|
|
|
// Python3-specific properties, including whether Python3 is supported for this module
|
|
// and version-specific sources, exclusions and dependencies.
|
|
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"`
|
|
|
|
// whether the module is required to be built with actual_version.
|
|
// this is set by the python version mutator based on version-specific properties
|
|
Enabled *bool `blueprint:"mutated"`
|
|
|
|
// whether the binary is required to be built with embedded launcher for this actual_version.
|
|
// this is set by the python version mutator based on version-specific properties
|
|
Embedded_launcher *bool `blueprint:"mutated"`
|
|
}
|
|
|
|
// Used to store files of current module after expanding dependencies
|
|
type pathMapping struct {
|
|
dest string
|
|
src android.Path
|
|
}
|
|
|
|
type PythonLibraryModule struct {
|
|
android.ModuleBase
|
|
android.DefaultableModuleBase
|
|
android.BazelModuleBase
|
|
|
|
properties BaseProperties
|
|
protoProperties android.ProtoProperties
|
|
|
|
// initialize before calling Init
|
|
hod android.HostOrDeviceSupported
|
|
multilib android.Multilib
|
|
|
|
// 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 file containing the current module's source/data files.
|
|
srcsZip android.Path
|
|
|
|
// The zip file containing the current module's source/data files, with the
|
|
// source files precompiled.
|
|
precompiledSrcsZip android.Path
|
|
}
|
|
|
|
// newModule generates new Python base module
|
|
func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *PythonLibraryModule {
|
|
return &PythonLibraryModule{
|
|
hod: hod,
|
|
multilib: multilib,
|
|
}
|
|
}
|
|
|
|
// interface implemented by Python modules to provide source and data mappings and zip to python
|
|
// modules that depend on it
|
|
type pythonDependency interface {
|
|
getSrcsPathMappings() []pathMapping
|
|
getDataPathMappings() []pathMapping
|
|
getSrcsZip() android.Path
|
|
getPrecompiledSrcsZip() android.Path
|
|
getPkgPath() string
|
|
}
|
|
|
|
// getSrcsPathMappings gets this module's path mapping of src source path : runfiles destination
|
|
func (p *PythonLibraryModule) getSrcsPathMappings() []pathMapping {
|
|
return p.srcsPathMappings
|
|
}
|
|
|
|
// getSrcsPathMappings gets this module's path mapping of data source path : runfiles destination
|
|
func (p *PythonLibraryModule) getDataPathMappings() []pathMapping {
|
|
return p.dataPathMappings
|
|
}
|
|
|
|
// getSrcsZip returns the filepath where the current module's source/data files are zipped.
|
|
func (p *PythonLibraryModule) getSrcsZip() android.Path {
|
|
return p.srcsZip
|
|
}
|
|
|
|
// getSrcsZip returns the filepath where the current module's source/data files are zipped.
|
|
func (p *PythonLibraryModule) getPrecompiledSrcsZip() android.Path {
|
|
return p.precompiledSrcsZip
|
|
}
|
|
|
|
// getPkgPath returns the pkg_path value
|
|
func (p *PythonLibraryModule) getPkgPath() string {
|
|
return String(p.properties.Pkg_path)
|
|
}
|
|
|
|
func (p *PythonLibraryModule) getBaseProperties() *BaseProperties {
|
|
return &p.properties
|
|
}
|
|
|
|
var _ pythonDependency = (*PythonLibraryModule)(nil)
|
|
|
|
func (p *PythonLibraryModule) init() android.Module {
|
|
p.AddProperties(&p.properties, &p.protoProperties)
|
|
android.InitAndroidArchModule(p, p.hod, p.multilib)
|
|
android.InitDefaultableModule(p)
|
|
android.InitBazelModule(p)
|
|
return p
|
|
}
|
|
|
|
// Python-specific tag to transfer information on the purpose of a dependency.
|
|
// This is used when adding a dependency on a module, which can later be accessed when visiting
|
|
// dependencies.
|
|
type dependencyTag struct {
|
|
blueprint.BaseDependencyTag
|
|
name string
|
|
}
|
|
|
|
// Python-specific tag that indicates that installed files of this module should depend on installed
|
|
// files of the dependency
|
|
type installDependencyTag struct {
|
|
blueprint.BaseDependencyTag
|
|
// embedding this struct provides the installation dependency requirement
|
|
android.InstallAlwaysNeededDependencyTag
|
|
name string
|
|
}
|
|
|
|
var (
|
|
pythonLibTag = dependencyTag{name: "pythonLib"}
|
|
javaDataTag = dependencyTag{name: "javaData"}
|
|
// The python interpreter, with soong module name "py3-launcher" or "py3-launcher-autorun".
|
|
launcherTag = dependencyTag{name: "launcher"}
|
|
launcherSharedLibTag = installDependencyTag{name: "launcherSharedLib"}
|
|
// The python interpreter built for host so that we can precompile python sources.
|
|
// This only works because the precompiled sources don't vary by architecture.
|
|
// The soong module name is "py3-launcher".
|
|
hostLauncherTag = dependencyTag{name: "hostLauncher"}
|
|
hostlauncherSharedLibTag = dependencyTag{name: "hostlauncherSharedLib"}
|
|
hostStdLibTag = dependencyTag{name: "hostStdLib"}
|
|
pathComponentRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
|
|
pyExt = ".py"
|
|
protoExt = ".proto"
|
|
pyVersion2 = "PY2"
|
|
pyVersion3 = "PY3"
|
|
pyVersion2And3 = "PY2ANDPY3"
|
|
internalPath = "internal"
|
|
)
|
|
|
|
type basePropertiesProvider interface {
|
|
getBaseProperties() *BaseProperties
|
|
}
|
|
|
|
// versionSplitMutator creates version variants for modules and appends the version-specific
|
|
// properties for a given variant to the properties in the variant module
|
|
func versionSplitMutator() func(android.BottomUpMutatorContext) {
|
|
return func(mctx android.BottomUpMutatorContext) {
|
|
if base, ok := mctx.Module().(basePropertiesProvider); ok {
|
|
props := base.getBaseProperties()
|
|
var versionNames []string
|
|
// collect version specific properties, so that we can merge version-specific properties
|
|
// into the module's overall properties
|
|
var versionProps []VersionProperties
|
|
// PY3 is first so that we alias the PY3 variant rather than PY2 if both
|
|
// are available
|
|
if proptools.BoolDefault(props.Version.Py3.Enabled, true) {
|
|
versionNames = append(versionNames, pyVersion3)
|
|
versionProps = append(versionProps, props.Version.Py3)
|
|
}
|
|
if proptools.BoolDefault(props.Version.Py2.Enabled, false) {
|
|
if !mctx.DeviceConfig().BuildBrokenUsesSoongPython2Modules() &&
|
|
mctx.ModuleName() != "py2-cmd" &&
|
|
mctx.ModuleName() != "py2-stdlib" {
|
|
mctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3. This error can be temporarily overridden by setting BUILD_BROKEN_USES_SOONG_PYTHON2_MODULES := true in the product configuration")
|
|
}
|
|
versionNames = append(versionNames, pyVersion2)
|
|
versionProps = append(versionProps, props.Version.Py2)
|
|
}
|
|
modules := mctx.CreateLocalVariations(versionNames...)
|
|
// Alias module to the first variant
|
|
if len(versionNames) > 0 {
|
|
mctx.AliasVariation(versionNames[0])
|
|
}
|
|
for i, v := range versionNames {
|
|
// set the actual version for Python module.
|
|
newProps := modules[i].(basePropertiesProvider).getBaseProperties()
|
|
newProps.Actual_version = v
|
|
// append versioned properties for the Python module to the overall properties
|
|
err := proptools.AppendMatchingProperties([]interface{}{newProps}, &versionProps[i], nil)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func anyHasExt(paths []string, ext string) bool {
|
|
for _, p := range paths {
|
|
if filepath.Ext(p) == ext {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (p *PythonLibraryModule) anySrcHasExt(ctx android.BottomUpMutatorContext, ext string) bool {
|
|
return anyHasExt(p.properties.Srcs, ext)
|
|
}
|
|
|
|
// DepsMutator mutates dependencies for this module:
|
|
// - handles proto dependencies,
|
|
// - if required, specifies launcher and adds launcher dependencies,
|
|
// - applies python version mutations to Python dependencies
|
|
func (p *PythonLibraryModule) DepsMutator(ctx android.BottomUpMutatorContext) {
|
|
android.ProtoDeps(ctx, &p.protoProperties)
|
|
|
|
versionVariation := []blueprint.Variation{
|
|
{"python_version", p.properties.Actual_version},
|
|
}
|
|
|
|
// If sources contain a proto file, add dependency on libprotobuf-python
|
|
if p.anySrcHasExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
|
|
ctx.AddVariationDependencies(versionVariation, pythonLibTag, "libprotobuf-python")
|
|
}
|
|
|
|
// Add python library dependencies for this python version variation
|
|
ctx.AddVariationDependencies(versionVariation, pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
|
|
|
|
// Emulate the data property for java_data but with the arch variation overridden to "common"
|
|
// so that it can point to java modules.
|
|
javaDataVariation := []blueprint.Variation{{"arch", android.Common.String()}}
|
|
ctx.AddVariationDependencies(javaDataVariation, javaDataTag, p.properties.Java_data...)
|
|
|
|
p.AddDepsOnPythonLauncherAndStdlib(ctx, hostStdLibTag, hostLauncherTag, hostlauncherSharedLibTag, false, ctx.Config().BuildOSTarget)
|
|
}
|
|
|
|
// AddDepsOnPythonLauncherAndStdlib will make the current module depend on the python stdlib,
|
|
// launcher (interpreter), and the launcher's shared libraries. If autorun is true, it will use
|
|
// the autorun launcher instead of the regular one. This function acceps a targetForDeps argument
|
|
// as the target to use for these dependencies. For embedded launcher python binaries, the launcher
|
|
// that will be embedded will be under the same target as the python module itself. But when
|
|
// precompiling python code, we need to get the python launcher built for host, even if we're
|
|
// compiling the python module for device, so we pass a different target to this function.
|
|
func (p *PythonLibraryModule) AddDepsOnPythonLauncherAndStdlib(ctx android.BottomUpMutatorContext,
|
|
stdLibTag, launcherTag, launcherSharedLibTag blueprint.DependencyTag,
|
|
autorun bool, targetForDeps android.Target) {
|
|
var stdLib string
|
|
var launcherModule string
|
|
// Add launcher shared lib dependencies. Ideally, these should be
|
|
// derived from the `shared_libs` property of the launcher. TODO: read these from
|
|
// the python launcher itself using ctx.OtherModuleProvider() or similar on the result
|
|
// of ctx.AddFarVariationDependencies()
|
|
launcherSharedLibDeps := []string{
|
|
"libsqlite",
|
|
}
|
|
// Add launcher-specific dependencies for bionic
|
|
if targetForDeps.Os.Bionic() {
|
|
launcherSharedLibDeps = append(launcherSharedLibDeps, "libc", "libdl", "libm")
|
|
}
|
|
if targetForDeps.Os == android.LinuxMusl && !ctx.Config().HostStaticBinaries() {
|
|
launcherSharedLibDeps = append(launcherSharedLibDeps, "libc_musl")
|
|
}
|
|
|
|
switch p.properties.Actual_version {
|
|
case pyVersion2:
|
|
stdLib = "py2-stdlib"
|
|
|
|
launcherModule = "py2-launcher"
|
|
if autorun {
|
|
launcherModule = "py2-launcher-autorun"
|
|
}
|
|
|
|
launcherSharedLibDeps = append(launcherSharedLibDeps, "libc++")
|
|
case pyVersion3:
|
|
var prebuiltStdLib bool
|
|
if targetForDeps.Os.Bionic() {
|
|
prebuiltStdLib = false
|
|
} else if ctx.Config().VendorConfig("cpython3").Bool("force_build_host") {
|
|
prebuiltStdLib = false
|
|
} else {
|
|
prebuiltStdLib = true
|
|
}
|
|
|
|
if prebuiltStdLib {
|
|
stdLib = "py3-stdlib-prebuilt"
|
|
} else {
|
|
stdLib = "py3-stdlib"
|
|
}
|
|
|
|
launcherModule = "py3-launcher"
|
|
if autorun {
|
|
launcherModule = "py3-launcher-autorun"
|
|
}
|
|
if ctx.Config().HostStaticBinaries() && targetForDeps.Os == android.LinuxMusl {
|
|
launcherModule += "-static"
|
|
}
|
|
if ctx.Device() {
|
|
launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
|
|
}
|
|
default:
|
|
panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.",
|
|
p.properties.Actual_version, ctx.ModuleName()))
|
|
}
|
|
targetVariations := targetForDeps.Variations()
|
|
if ctx.ModuleName() != stdLib {
|
|
stdLibVariations := make([]blueprint.Variation, 0, len(targetVariations)+1)
|
|
stdLibVariations = append(stdLibVariations, blueprint.Variation{Mutator: "python_version", Variation: p.properties.Actual_version})
|
|
stdLibVariations = append(stdLibVariations, targetVariations...)
|
|
// Using AddFarVariationDependencies for all of these because they can be for a different
|
|
// platform, like if the python module itself was being compiled for device, we may want
|
|
// the python interpreter built for host so that we can precompile python sources.
|
|
ctx.AddFarVariationDependencies(stdLibVariations, stdLibTag, stdLib)
|
|
}
|
|
ctx.AddFarVariationDependencies(targetVariations, launcherTag, launcherModule)
|
|
ctx.AddFarVariationDependencies(targetVariations, launcherSharedLibTag, launcherSharedLibDeps...)
|
|
}
|
|
|
|
// GenerateAndroidBuildActions performs build actions common to all Python modules
|
|
func (p *PythonLibraryModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
|
|
expandedSrcs := android.PathsForModuleSrcExcludes(ctx, p.properties.Srcs, p.properties.Exclude_srcs)
|
|
|
|
// expand data files from "data" property.
|
|
expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
|
|
|
|
// Emulate the data property for java_data dependencies.
|
|
for _, javaData := range ctx.GetDirectDepsWithTag(javaDataTag) {
|
|
expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
|
|
}
|
|
|
|
// Validate pkg_path property
|
|
pkgPath := String(p.properties.Pkg_path)
|
|
if pkgPath != "" {
|
|
// TODO: export validation from android/paths.go handling to replace this duplicated functionality
|
|
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 property Is_internal is set, prepend pkgPath with internalPath
|
|
if proptools.BoolDefault(p.properties.Is_internal, false) {
|
|
pkgPath = filepath.Join(internalPath, pkgPath)
|
|
}
|
|
|
|
// generate src:destination path mappings for this module
|
|
p.genModulePathMappings(ctx, pkgPath, expandedSrcs, expandedData)
|
|
|
|
// generate the zipfile of all source and data files
|
|
p.srcsZip = p.createSrcsZip(ctx, pkgPath)
|
|
p.precompiledSrcsZip = p.precompileSrcs(ctx)
|
|
}
|
|
|
|
func isValidPythonPath(path string) error {
|
|
identifiers := strings.Split(strings.TrimSuffix(path, filepath.Ext(path)), "/")
|
|
for _, token := range identifiers {
|
|
if !pathComponentRegexp.MatchString(token) {
|
|
return fmt.Errorf("the path %q contains invalid subpath %q. "+
|
|
"Subpaths must be at least one character long. "+
|
|
"The first character must an underscore or letter. "+
|
|
"Following characters may be any of: letter, digit, underscore, hyphen.",
|
|
path, token)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// For this module, generate unique pathMappings: <dest: runfiles_path, src: source_path>
|
|
// for python/data files expanded from properties.
|
|
func (p *PythonLibraryModule) 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)
|
|
|
|
// Disable path checks for the stdlib, as it includes a "." in the version string
|
|
isInternal := proptools.BoolDefault(p.properties.Is_internal, false)
|
|
|
|
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())
|
|
if !isInternal {
|
|
if err := isValidPythonPath(runfilesPath); err != nil {
|
|
ctx.PropertyErrorf("srcs", err.Error())
|
|
}
|
|
}
|
|
if !checkForDuplicateOutputPath(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 !checkForDuplicateOutputPath(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) {
|
|
p.dataPathMappings = append(p.dataPathMappings,
|
|
pathMapping{dest: runfilesPath, src: d})
|
|
}
|
|
}
|
|
}
|
|
|
|
// createSrcsZip registers build actions to zip current module's sources and data.
|
|
func (p *PythonLibraryModule) createSrcsZip(ctx android.ModuleContext, pkgPath string) android.Path {
|
|
relativeRootMap := make(map[string]android.Paths)
|
|
var protoSrcs android.Paths
|
|
addPathMapping := func(path pathMapping) {
|
|
// handle proto sources separately
|
|
if path.src.Ext() == protoExt {
|
|
protoSrcs = append(protoSrcs, path.src)
|
|
} else {
|
|
relativeRoot := strings.TrimSuffix(path.src.String(), path.src.Rel())
|
|
relativeRootMap[relativeRoot] = append(relativeRootMap[relativeRoot], path.src)
|
|
}
|
|
}
|
|
|
|
// "srcs" or "data" properties may contain filegroups so it might happen that
|
|
// the root directory for each source path is different.
|
|
for _, path := range p.srcsPathMappings {
|
|
addPathMapping(path)
|
|
}
|
|
for _, path := range p.dataPathMappings {
|
|
addPathMapping(path)
|
|
}
|
|
|
|
var zips android.Paths
|
|
if len(protoSrcs) > 0 {
|
|
protoFlags := android.GetProtoFlags(ctx, &p.protoProperties)
|
|
protoFlags.OutTypeFlag = "--python_out"
|
|
|
|
if pkgPath != "" {
|
|
pkgPathStagingDir := android.PathForModuleGen(ctx, "protos_staged_for_pkg_path")
|
|
rule := android.NewRuleBuilder(pctx, ctx)
|
|
var stagedProtoSrcs android.Paths
|
|
for _, srcFile := range protoSrcs {
|
|
stagedProtoSrc := pkgPathStagingDir.Join(ctx, pkgPath, srcFile.Rel())
|
|
rule.Command().Text("mkdir -p").Flag(filepath.Base(stagedProtoSrc.String()))
|
|
rule.Command().Text("cp -f").Input(srcFile).Output(stagedProtoSrc)
|
|
stagedProtoSrcs = append(stagedProtoSrcs, stagedProtoSrc)
|
|
}
|
|
rule.Build("stage_protos_for_pkg_path", "Stage protos for pkg_path")
|
|
protoSrcs = stagedProtoSrcs
|
|
}
|
|
|
|
for _, srcFile := range protoSrcs {
|
|
zip := genProto(ctx, srcFile, protoFlags)
|
|
zips = append(zips, zip)
|
|
}
|
|
}
|
|
|
|
if len(relativeRootMap) > 0 {
|
|
// in order to keep stable order of soong_zip params, we sort the keys here.
|
|
roots := android.SortedKeys(relativeRootMap)
|
|
|
|
// Use -symlinks=false so that the symlinks in the bazel output directory are followed
|
|
parArgs := []string{"-symlinks=false"}
|
|
if pkgPath != "" {
|
|
// use package path as path prefix
|
|
parArgs = append(parArgs, `-P `+pkgPath)
|
|
}
|
|
paths := android.Paths{}
|
|
for _, root := range roots {
|
|
// specify relative root of file in following -f arguments
|
|
parArgs = append(parArgs, `-C `+root)
|
|
for _, path := range relativeRootMap[root] {
|
|
parArgs = append(parArgs, `-f `+path.String())
|
|
paths = append(paths, path)
|
|
}
|
|
}
|
|
|
|
origSrcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".py.srcszip")
|
|
ctx.Build(pctx, android.BuildParams{
|
|
Rule: zip,
|
|
Description: "python library archive",
|
|
Output: origSrcsZip,
|
|
// as zip rule does not use $in, there is no real need to distinguish between Inputs and Implicits
|
|
Implicits: paths,
|
|
Args: map[string]string{
|
|
"args": strings.Join(parArgs, " "),
|
|
},
|
|
})
|
|
zips = append(zips, origSrcsZip)
|
|
}
|
|
// we may have multiple zips due to separate handling of proto source files
|
|
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 (p *PythonLibraryModule) precompileSrcs(ctx android.ModuleContext) android.Path {
|
|
// To precompile the python sources, we need a python interpreter and stdlib built
|
|
// for host. We then use those to compile the python sources, which may be used on either
|
|
// host of device. Python bytecode is architecture agnostic, so we're essentially
|
|
// "cross compiling" for device here purely by virtue of host and device python bytecode
|
|
// being the same.
|
|
var stdLib android.Path
|
|
var stdLibPkg string
|
|
var launcher android.Path
|
|
if proptools.BoolDefault(p.properties.Is_internal, false) {
|
|
stdLib = p.srcsZip
|
|
stdLibPkg = p.getPkgPath()
|
|
} else {
|
|
ctx.VisitDirectDepsWithTag(hostStdLibTag, func(module android.Module) {
|
|
if dep, ok := module.(pythonDependency); ok {
|
|
stdLib = dep.getPrecompiledSrcsZip()
|
|
stdLibPkg = dep.getPkgPath()
|
|
}
|
|
})
|
|
}
|
|
ctx.VisitDirectDepsWithTag(hostLauncherTag, func(module android.Module) {
|
|
if dep, ok := module.(IntermPathProvider); ok {
|
|
optionalLauncher := dep.IntermPathForModuleOut()
|
|
if optionalLauncher.Valid() {
|
|
launcher = optionalLauncher.Path()
|
|
}
|
|
}
|
|
})
|
|
var launcherSharedLibs android.Paths
|
|
var ldLibraryPath []string
|
|
ctx.VisitDirectDepsWithTag(hostlauncherSharedLibTag, func(module android.Module) {
|
|
if dep, ok := module.(IntermPathProvider); ok {
|
|
optionalPath := dep.IntermPathForModuleOut()
|
|
if optionalPath.Valid() {
|
|
launcherSharedLibs = append(launcherSharedLibs, optionalPath.Path())
|
|
ldLibraryPath = append(ldLibraryPath, filepath.Dir(optionalPath.Path().String()))
|
|
}
|
|
}
|
|
})
|
|
|
|
out := android.PathForModuleOut(ctx, ctx.ModuleName()+".srcszipprecompiled")
|
|
if stdLib == nil || launcher == nil {
|
|
// This shouldn't happen in a real build because we'll error out when adding dependencies
|
|
// on the stdlib and launcher if they don't exist. But some tests set
|
|
// AllowMissingDependencies.
|
|
return out
|
|
}
|
|
ctx.Build(pctx, android.BuildParams{
|
|
Rule: precompile,
|
|
Input: p.srcsZip,
|
|
Output: out,
|
|
Implicits: launcherSharedLibs,
|
|
Description: "Precompile the python sources of " + ctx.ModuleName(),
|
|
Args: map[string]string{
|
|
"stdlibZip": stdLib.String(),
|
|
"stdlibPkg": stdLibPkg,
|
|
"launcher": launcher.String(),
|
|
"ldLibraryPath": strings.Join(ldLibraryPath, ":"),
|
|
},
|
|
})
|
|
return out
|
|
}
|
|
|
|
// isPythonLibModule returns whether the given module is a Python library PythonLibraryModule or not
|
|
func isPythonLibModule(module blueprint.Module) bool {
|
|
if _, ok := module.(*PythonLibraryModule); ok {
|
|
if _, ok := module.(*PythonBinaryModule); !ok {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// collectPathsFromTransitiveDeps checks for source/data files for duplicate paths
|
|
// for module and its transitive dependencies and collects list of data/source file
|
|
// zips for transitive dependencies.
|
|
func (p *PythonLibraryModule) collectPathsFromTransitiveDeps(ctx android.ModuleContext, precompiled bool) android.Paths {
|
|
// 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)
|
|
|
|
var result android.Paths
|
|
|
|
// visit all its dependencies in depth first.
|
|
ctx.WalkDeps(func(child, parent android.Module) bool {
|
|
// we only collect dependencies tagged as python library deps
|
|
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) {
|
|
ctx.PropertyErrorf("libs",
|
|
"the dependency %q of module %q is not Python library!",
|
|
ctx.OtherModuleName(child), ctx.ModuleName())
|
|
}
|
|
// collect source and data paths, checking that there are no duplicate output file conflicts
|
|
if dep, ok := child.(pythonDependency); ok {
|
|
srcs := dep.getSrcsPathMappings()
|
|
for _, path := range srcs {
|
|
checkForDuplicateOutputPath(ctx, destToPySrcs,
|
|
path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
|
|
}
|
|
data := dep.getDataPathMappings()
|
|
for _, path := range data {
|
|
checkForDuplicateOutputPath(ctx, destToPyData,
|
|
path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
|
|
}
|
|
if precompiled {
|
|
result = append(result, dep.getPrecompiledSrcsZip())
|
|
} else {
|
|
result = append(result, dep.getSrcsZip())
|
|
}
|
|
}
|
|
return true
|
|
})
|
|
return result
|
|
}
|
|
|
|
// chckForDuplicateOutputPath checks whether outputPath has already been included in map m, which
|
|
// would result in two files being placed in the same location.
|
|
// If there is a duplicate path, an error is thrown and true is returned
|
|
// Otherwise, outputPath: srcPath is added to m and returns false
|
|
func checkForDuplicateOutputPath(ctx android.ModuleContext, m map[string]string, outputPath, srcPath, curModule, otherModule string) bool {
|
|
if oldSrcPath, found := m[outputPath]; 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.",
|
|
outputPath, curModule, oldSrcPath, otherModule, srcPath)
|
|
return true
|
|
}
|
|
m[outputPath] = srcPath
|
|
|
|
return false
|
|
}
|
|
|
|
// InstallInData returns true as Python is not supported in the system partition
|
|
func (p *PythonLibraryModule) InstallInData() bool {
|
|
return true
|
|
}
|
|
|
|
var Bool = proptools.Bool
|
|
var BoolDefault = proptools.BoolDefault
|
|
var String = proptools.String
|