• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 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
17// This file contains the "Base" module type for building Python program.
18
19import (
20	"fmt"
21	"path/filepath"
22	"regexp"
23	"sort"
24	"strings"
25
26	"android/soong/cc"
27
28	"github.com/google/blueprint"
29	"github.com/google/blueprint/depset"
30	"github.com/google/blueprint/proptools"
31
32	"android/soong/android"
33)
34
35type PythonLibraryInfo struct {
36	SrcsPathMappings   []pathMapping
37	DataPathMappings   []pathMapping
38	SrcsZip            android.Path
39	PrecompiledSrcsZip android.Path
40	PkgPath            string
41	BundleSharedLibs   android.Paths
42}
43
44var PythonLibraryInfoProvider = blueprint.NewProvider[PythonLibraryInfo]()
45
46// the version-specific properties that apply to python modules.
47type VersionProperties struct {
48	// whether the module is required to be built with this version.
49	// Defaults to true for Python 3, and false otherwise.
50	Enabled *bool
51
52	// list of source files specific to this Python version.
53	// Using the syntax ":module", srcs may reference the outputs of other modules that produce source files,
54	// e.g. genrule or filegroup.
55	Srcs []string `android:"path,arch_variant"`
56
57	// list of source files that should not be used to build the Python module for this version.
58	// This is most useful to remove files that are not common to all Python versions.
59	Exclude_srcs []string `android:"path,arch_variant"`
60
61	// list of the Python libraries used only for this Python version.
62	Libs []string `android:"arch_variant"`
63
64	// whether the binary is required to be built with embedded launcher for this version, defaults to true.
65	Embedded_launcher *bool // TODO(b/174041232): Remove this property
66}
67
68// properties that apply to all python modules
69type BaseProperties struct {
70	// the package path prefix within the output artifact at which to place the source/data
71	// files of the current module.
72	// eg. Pkg_path = "a/b/c"; Other packages can reference this module by using
73	// (from a.b.c import ...) statement.
74	// if left unspecified, all the source/data files path is unchanged within zip file.
75	Pkg_path *string
76
77	// true, if the Python module is used internally, eg, Python std libs.
78	Is_internal *bool
79
80	// list of source (.py) files compatible both with Python2 and Python3 used to compile the
81	// Python module.
82	// srcs may reference the outputs of other modules that produce source files like genrule
83	// or filegroup using the syntax ":module".
84	// Srcs has to be non-empty.
85	Srcs []string `android:"path,arch_variant"`
86
87	// list of source files that should not be used to build the C/C++ module.
88	// This is most useful in the arch/multilib variants to remove non-common files
89	Exclude_srcs []string `android:"path,arch_variant"`
90
91	// list of files or filegroup modules that provide data that should be installed alongside
92	// the test. the file extension can be arbitrary except for (.py).
93	Data []string `android:"path,arch_variant"`
94
95	// Same as data, but will add dependencies on modules using the device's os variation and
96	// the common arch variation. Useful for a host test that wants to embed a module built for
97	// device.
98	Device_common_data []string `android:"path_device_common"`
99
100	// Same as data, but will add dependencies on modules via a device os variation and the
101	// device's first supported arch's variation. Useful for a host test that wants to embed a
102	// module built for device.
103	Device_first_data []string `android:"path_device_first"`
104
105	// list of java modules that provide data that should be installed alongside the test.
106	Java_data []string
107
108	// list of the Python libraries compatible both with Python2 and Python3.
109	Libs []string `android:"arch_variant"`
110
111	// TODO: b/403060602 - add unit tests for this property and related code
112	// list of shared libraries that should be packaged with the python code for this module.
113	Shared_libs []string `android:"arch_variant"`
114
115	Version struct {
116		// Python2-specific properties, including whether Python2 is supported for this module
117		// and version-specific sources, exclusions and dependencies.
118		Py2 VersionProperties `android:"arch_variant"`
119
120		// Python3-specific properties, including whether Python3 is supported for this module
121		// and version-specific sources, exclusions and dependencies.
122		Py3 VersionProperties `android:"arch_variant"`
123	} `android:"arch_variant"`
124
125	// This enabled property is to accept the collapsed enabled property from the VersionProperties.
126	// It is unused now, as all builds should be python3.
127	Enabled *bool `blueprint:"mutated"`
128
129	// whether the binary is required to be built with an embedded python interpreter, defaults to
130	// true. This allows taking the resulting binary outside of the build and running it on machines
131	// that don't have python installed or may have an older version of python.
132	Embedded_launcher *bool
133}
134
135// Used to store files of current module after expanding dependencies
136type pathMapping struct {
137	dest string
138	src  android.Path
139}
140
141type PythonLibraryModule struct {
142	android.ModuleBase
143	android.DefaultableModuleBase
144
145	properties      BaseProperties
146	protoProperties android.ProtoProperties
147
148	// initialize before calling Init
149	hod      android.HostOrDeviceSupported
150	multilib android.Multilib
151
152	// the Python files of current module after expanding source dependencies.
153	// pathMapping: <dest: runfile_path, src: source_path>
154	srcsPathMappings []pathMapping
155
156	// the data files of current module after expanding source dependencies.
157	// pathMapping: <dest: runfile_path, src: source_path>
158	dataPathMappings []pathMapping
159
160	// The zip file containing the current module's source/data files.
161	srcsZip android.Path
162
163	// The zip file containing the current module's source/data files, with the
164	// source files precompiled.
165	precompiledSrcsZip android.Path
166
167	sourceProperties android.SourceProperties
168
169	// The shared libraries that should be bundled with the python code for
170	// any standalone python binaries that depend on this module.
171	bundleSharedLibs android.Paths
172}
173
174// newModule generates new Python base module
175func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *PythonLibraryModule {
176	return &PythonLibraryModule{
177		hod:      hod,
178		multilib: multilib,
179	}
180}
181
182// getSrcsPathMappings gets this module's path mapping of src source path : runfiles destination
183func (p *PythonLibraryModule) getSrcsPathMappings() []pathMapping {
184	return p.srcsPathMappings
185}
186
187// getSrcsPathMappings gets this module's path mapping of data source path : runfiles destination
188func (p *PythonLibraryModule) getDataPathMappings() []pathMapping {
189	return p.dataPathMappings
190}
191
192// getSrcsZip returns the filepath where the current module's source/data files are zipped.
193func (p *PythonLibraryModule) getSrcsZip() android.Path {
194	return p.srcsZip
195}
196
197// getSrcsZip returns the filepath where the current module's source/data files are zipped.
198func (p *PythonLibraryModule) getPrecompiledSrcsZip() android.Path {
199	return p.precompiledSrcsZip
200}
201
202// getPkgPath returns the pkg_path value
203func (p *PythonLibraryModule) getPkgPath() string {
204	return String(p.properties.Pkg_path)
205}
206
207func (p *PythonLibraryModule) getBaseProperties() *BaseProperties {
208	return &p.properties
209}
210
211func (p *PythonLibraryModule) getBundleSharedLibs() android.Paths {
212	return p.bundleSharedLibs
213}
214
215func (p *PythonLibraryModule) init() android.Module {
216	p.AddProperties(&p.properties, &p.protoProperties, &p.sourceProperties)
217	android.InitAndroidArchModule(p, p.hod, p.multilib)
218	android.InitDefaultableModule(p)
219	return p
220}
221
222// Python-specific tag to transfer information on the purpose of a dependency.
223// This is used when adding a dependency on a module, which can later be accessed when visiting
224// dependencies.
225type dependencyTag struct {
226	blueprint.BaseDependencyTag
227	name string
228}
229
230// Python-specific tag that indicates that installed files of this module should depend on installed
231// files of the dependency
232type installDependencyTag struct {
233	blueprint.BaseDependencyTag
234	// embedding this struct provides the installation dependency requirement
235	android.InstallAlwaysNeededDependencyTag
236	name string
237}
238
239var (
240	pythonLibTag = dependencyTag{name: "pythonLib"}
241	javaDataTag  = dependencyTag{name: "javaData"}
242	sharedLibTag = dependencyTag{name: "sharedLib"}
243	// The python interpreter, with soong module name "py3-launcher" or "py3-launcher-autorun".
244	launcherTag          = dependencyTag{name: "launcher"}
245	launcherSharedLibTag = installDependencyTag{name: "launcherSharedLib"}
246	// The python interpreter built for host so that we can precompile python sources.
247	// This only works because the precompiled sources don't vary by architecture.
248	// The soong module name is "py3-launcher".
249	hostLauncherTag          = dependencyTag{name: "hostLauncher"}
250	hostlauncherSharedLibTag = dependencyTag{name: "hostlauncherSharedLib"}
251	hostStdLibTag            = dependencyTag{name: "hostStdLib"}
252	pathComponentRegexp      = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`)
253	pyExt                    = ".py"
254	protoExt                 = ".proto"
255	internalPath             = "internal"
256)
257
258type basePropertiesProvider interface {
259	getBaseProperties() *BaseProperties
260}
261
262func anyHasExt(paths []string, ext string) bool {
263	for _, p := range paths {
264		if filepath.Ext(p) == ext {
265			return true
266		}
267	}
268
269	return false
270}
271
272func (p *PythonLibraryModule) anySrcHasExt(ctx android.BottomUpMutatorContext, ext string) bool {
273	return anyHasExt(p.properties.Srcs, ext)
274}
275
276// DepsMutator mutates dependencies for this module:
277//   - handles proto dependencies,
278//   - if required, specifies launcher and adds launcher dependencies,
279//   - applies python version mutations to Python dependencies
280func (p *PythonLibraryModule) DepsMutator(ctx android.BottomUpMutatorContext) {
281	// Flatten the version.py3 props down into the main property struct. Leftover from when
282	// there was both python2 and 3 in the build, and properties could be different between them.
283	if base, ok := ctx.Module().(basePropertiesProvider); ok {
284		props := base.getBaseProperties()
285
286		err := proptools.AppendMatchingProperties([]interface{}{props}, &props.Version.Py3, nil)
287		if err != nil {
288			panic(err)
289		}
290	}
291
292	android.ProtoDeps(ctx, &p.protoProperties)
293
294	// If sources contain a proto file, add dependency on libprotobuf-python
295	if p.anySrcHasExt(ctx, protoExt) && p.Name() != "libprotobuf-python" {
296		ctx.AddDependency(ctx.Module(), pythonLibTag, "libprotobuf-python")
297	}
298
299	// Add python library dependencies for this python version variation
300	ctx.AddDependency(ctx.Module(), pythonLibTag, android.LastUniqueStrings(p.properties.Libs)...)
301
302	// Emulate the data property for java_data but with the arch variation overridden to "common"
303	// so that it can point to java modules.
304	javaDataVariation := []blueprint.Variation{{"arch", android.Common.String()}}
305	ctx.AddVariationDependencies(javaDataVariation, javaDataTag, p.properties.Java_data...)
306
307	if ctx.Host() {
308		ctx.AddVariationDependencies(ctx.Config().BuildOSTarget.Variations(), sharedLibTag, p.properties.Shared_libs...)
309	} else if len(p.properties.Shared_libs) > 0 {
310		ctx.PropertyErrorf("shared_libs", "shared_libs is not supported for device builds")
311	}
312
313	p.AddDepsOnPythonLauncherAndStdlib(ctx, hostStdLibTag, hostLauncherTag, hostlauncherSharedLibTag, false, ctx.Config().BuildOSTarget)
314}
315
316// AddDepsOnPythonLauncherAndStdlib will make the current module depend on the python stdlib,
317// launcher (interpreter), and the launcher's shared libraries. If autorun is true, it will use
318// the autorun launcher instead of the regular one. This function acceps a targetForDeps argument
319// as the target to use for these dependencies. For embedded launcher python binaries, the launcher
320// that will be embedded will be under the same target as the python module itself. But when
321// precompiling python code, we need to get the python launcher built for host, even if we're
322// compiling the python module for device, so we pass a different target to this function.
323func (p *PythonLibraryModule) AddDepsOnPythonLauncherAndStdlib(ctx android.BottomUpMutatorContext,
324	stdLibTag, launcherTag, launcherSharedLibTag blueprint.DependencyTag,
325	autorun bool, targetForDeps android.Target) {
326	var stdLib string
327	var launcherModule string
328	// Add launcher shared lib dependencies. Ideally, these should be
329	// derived from the `shared_libs` property of the launcher. TODO: read these from
330	// the python launcher itself using ctx.OtherModuleProvider() or similar on the result
331	// of ctx.AddFarVariationDependencies()
332	launcherSharedLibDeps := []string{
333		"libsqlite",
334	}
335	// Add launcher-specific dependencies for bionic
336	if targetForDeps.Os.Bionic() {
337		launcherSharedLibDeps = append(launcherSharedLibDeps, "libc", "libdl", "libm")
338	}
339	if targetForDeps.Os == android.LinuxMusl && !ctx.Config().HostStaticBinaries() {
340		launcherSharedLibDeps = append(launcherSharedLibDeps, "libc_musl")
341	}
342
343	var prebuiltStdLib bool
344	if targetForDeps.Os.Bionic() {
345		prebuiltStdLib = false
346	} else if ctx.Config().VendorConfig("cpython3").Bool("force_build_host") {
347		prebuiltStdLib = false
348	} else {
349		prebuiltStdLib = true
350	}
351
352	if prebuiltStdLib {
353		stdLib = "py3-stdlib-prebuilt"
354	} else {
355		stdLib = "py3-stdlib"
356	}
357
358	launcherModule = "py3-launcher"
359	if autorun {
360		launcherModule = "py3-launcher-autorun"
361	}
362	if ctx.Config().HostStaticBinaries() && targetForDeps.Os == android.LinuxMusl {
363		launcherModule += "-static"
364	}
365	if ctx.Device() {
366		launcherSharedLibDeps = append(launcherSharedLibDeps, "liblog")
367	}
368
369	targetVariations := targetForDeps.Variations()
370	if ctx.ModuleName() != stdLib {
371		// Using AddFarVariationDependencies for all of these because they can be for a different
372		// platform, like if the python module itself was being compiled for device, we may want
373		// the python interpreter built for host so that we can precompile python sources.
374		ctx.AddFarVariationDependencies(targetVariations, stdLibTag, stdLib)
375	}
376	ctx.AddFarVariationDependencies(targetVariations, launcherTag, launcherModule)
377	ctx.AddFarVariationDependencies(targetVariations, launcherSharedLibTag, launcherSharedLibDeps...)
378}
379
380// GenerateAndroidBuildActions performs build actions common to all Python modules
381func (p *PythonLibraryModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
382	if proptools.BoolDefault(p.properties.Version.Py2.Enabled, false) {
383		ctx.PropertyErrorf("version.py2.enabled", "Python 2 is no longer supported, please convert to python 3.")
384	}
385	expandedSrcs := android.PathsForModuleSrcExcludes(ctx, p.properties.Srcs, p.properties.Exclude_srcs)
386	// Keep before any early returns.
387	android.SetProvider(ctx, android.TestOnlyProviderKey, android.TestModuleInformation{
388		TestOnly:       Bool(p.sourceProperties.Test_only),
389		TopLevelTarget: p.sourceProperties.Top_level_test_target,
390	})
391
392	// expand data files from "data" property.
393	expandedData := android.PathsForModuleSrc(ctx, p.properties.Data)
394	expandedData = append(expandedData, android.PathsForModuleSrc(ctx, p.properties.Device_common_data)...)
395	expandedData = append(expandedData, android.PathsForModuleSrc(ctx, p.properties.Device_first_data)...)
396
397	// Emulate the data property for java_data dependencies.
398	for _, javaData := range ctx.GetDirectDepsProxyWithTag(javaDataTag) {
399		expandedData = append(expandedData, android.OutputFilesForModule(ctx, javaData, "")...)
400	}
401
402	var directImplementationDeps android.Paths
403	var transitiveImplementationDeps []depset.DepSet[android.Path]
404	ctx.VisitDirectDepsProxyWithTag(sharedLibTag, func(dep android.ModuleProxy) {
405		sharedLibInfo, _ := android.OtherModuleProvider(ctx, dep, cc.SharedLibraryInfoProvider)
406		if sharedLibInfo.SharedLibrary != nil {
407			expandedData = append(expandedData, android.OutputFilesForModule(ctx, dep, "")...)
408			directImplementationDeps = append(directImplementationDeps, android.OutputFilesForModule(ctx, dep, "")...)
409			if info, ok := android.OtherModuleProvider(ctx, dep, cc.ImplementationDepInfoProvider); ok {
410				transitiveImplementationDeps = append(transitiveImplementationDeps, info.ImplementationDeps)
411				p.bundleSharedLibs = append(p.bundleSharedLibs, info.ImplementationDeps.ToList()...)
412			}
413		} else {
414			ctx.PropertyErrorf("shared_libs", "%q of type %q is not supported", dep.Name(), ctx.OtherModuleType(dep))
415		}
416	})
417	android.SetProvider(ctx, cc.ImplementationDepInfoProvider, &cc.ImplementationDepInfo{
418		ImplementationDeps: depset.New(depset.PREORDER, directImplementationDeps, transitiveImplementationDeps),
419	})
420
421	// Validate pkg_path property
422	pkgPath := String(p.properties.Pkg_path)
423	if pkgPath != "" {
424		// TODO: export validation from android/paths.go handling to replace this duplicated functionality
425		pkgPath = filepath.Clean(String(p.properties.Pkg_path))
426		if pkgPath == ".." || strings.HasPrefix(pkgPath, "../") ||
427			strings.HasPrefix(pkgPath, "/") {
428			ctx.PropertyErrorf("pkg_path",
429				"%q must be a relative path contained in par file.",
430				String(p.properties.Pkg_path))
431			return
432		}
433	}
434	// If property Is_internal is set, prepend pkgPath with internalPath
435	if proptools.BoolDefault(p.properties.Is_internal, false) {
436		pkgPath = filepath.Join(internalPath, pkgPath)
437	}
438
439	// generate src:destination path mappings for this module
440	p.genModulePathMappings(ctx, pkgPath, expandedSrcs, expandedData)
441
442	// generate the zipfile of all source and data files
443	p.srcsZip = p.createSrcsZip(ctx, pkgPath)
444	p.precompiledSrcsZip = p.precompileSrcs(ctx)
445
446	android.SetProvider(ctx, PythonLibraryInfoProvider, PythonLibraryInfo{
447		SrcsPathMappings:   p.getSrcsPathMappings(),
448		DataPathMappings:   p.getDataPathMappings(),
449		SrcsZip:            p.getSrcsZip(),
450		PkgPath:            p.getPkgPath(),
451		PrecompiledSrcsZip: p.getPrecompiledSrcsZip(),
452		BundleSharedLibs:   p.getBundleSharedLibs(),
453	})
454}
455
456func isValidPythonPath(path string) error {
457	identifiers := strings.Split(strings.TrimSuffix(path, filepath.Ext(path)), "/")
458	for _, token := range identifiers {
459		if !pathComponentRegexp.MatchString(token) {
460			return fmt.Errorf("the path %q contains invalid subpath %q. "+
461				"Subpaths must be at least one character long. "+
462				"The first character must an underscore or letter. "+
463				"Following characters may be any of: letter, digit, underscore, hyphen.",
464				path, token)
465		}
466	}
467	return nil
468}
469
470// For this module, generate unique pathMappings: <dest: runfiles_path, src: source_path>
471// for python/data files expanded from properties.
472func (p *PythonLibraryModule) genModulePathMappings(ctx android.ModuleContext, pkgPath string,
473	expandedSrcs, expandedData android.Paths) {
474	// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
475	// check current module duplicates.
476	destToPySrcs := make(map[string]string)
477	destToPyData := make(map[string]string)
478
479	// Disable path checks for the stdlib, as it includes a "." in the version string
480	isInternal := proptools.BoolDefault(p.properties.Is_internal, false)
481
482	for _, s := range expandedSrcs {
483		if s.Ext() != pyExt && s.Ext() != protoExt {
484			ctx.PropertyErrorf("srcs", "found non (.py|.proto) file: %q!", s.String())
485			continue
486		}
487		runfilesPath := filepath.Join(pkgPath, s.Rel())
488		if !isInternal {
489			if err := isValidPythonPath(runfilesPath); err != nil {
490				ctx.PropertyErrorf("srcs", err.Error())
491			}
492		}
493		if !checkForDuplicateOutputPath(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) {
494			p.srcsPathMappings = append(p.srcsPathMappings, pathMapping{dest: runfilesPath, src: s})
495		}
496	}
497
498	for _, d := range expandedData {
499		if d.Ext() == pyExt {
500			ctx.PropertyErrorf("data", "found (.py) file: %q!", d.String())
501			continue
502		}
503		runfilesPath := filepath.Join(pkgPath, d.Rel())
504		if !checkForDuplicateOutputPath(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) {
505			p.dataPathMappings = append(p.dataPathMappings,
506				pathMapping{dest: runfilesPath, src: d})
507		}
508	}
509}
510
511// createSrcsZip registers build actions to zip current module's sources and data.
512func (p *PythonLibraryModule) createSrcsZip(ctx android.ModuleContext, pkgPath string) android.Path {
513	relativeRootMap := make(map[string]android.Paths)
514	var protoSrcs android.Paths
515	addPathMapping := func(path pathMapping) {
516		relativeRoot := strings.TrimSuffix(path.src.String(), path.src.Rel())
517		relativeRootMap[relativeRoot] = append(relativeRootMap[relativeRoot], path.src)
518	}
519
520	// "srcs" or "data" properties may contain filegroups so it might happen that
521	// the root directory for each source path is different.
522	for _, path := range p.srcsPathMappings {
523		// handle proto sources separately
524		if path.src.Ext() == protoExt {
525			protoSrcs = append(protoSrcs, path.src)
526		} else {
527			addPathMapping(path)
528		}
529	}
530	for _, path := range p.dataPathMappings {
531		addPathMapping(path)
532	}
533
534	var zips android.Paths
535	if len(protoSrcs) > 0 {
536		protoFlags := android.GetProtoFlags(ctx, &p.protoProperties)
537		protoFlags.OutTypeFlag = "--python_out"
538
539		if pkgPath != "" {
540			pkgPathStagingDir := android.PathForModuleGen(ctx, "protos_staged_for_pkg_path")
541			rule := android.NewRuleBuilder(pctx, ctx)
542			var stagedProtoSrcs android.Paths
543			for _, srcFile := range protoSrcs {
544				stagedProtoSrc := pkgPathStagingDir.Join(ctx, pkgPath, srcFile.Rel())
545				rule.Command().Text("cp -f").Input(srcFile).Output(stagedProtoSrc)
546				stagedProtoSrcs = append(stagedProtoSrcs, stagedProtoSrc)
547			}
548			rule.Build("stage_protos_for_pkg_path", "Stage protos for pkg_path")
549			protoSrcs = stagedProtoSrcs
550		}
551
552		for _, srcFile := range protoSrcs {
553			zip := genProto(ctx, srcFile, protoFlags)
554			zips = append(zips, zip)
555		}
556	}
557
558	if len(relativeRootMap) > 0 {
559		// in order to keep stable order of soong_zip params, we sort the keys here.
560		roots := android.SortedKeys(relativeRootMap)
561
562		// Use -symlinks=false so that the symlinks in the bazel output directory are followed
563		parArgs := []string{"-symlinks=false"}
564		if pkgPath != "" {
565			// use package path as path prefix
566			parArgs = append(parArgs, `-P `+pkgPath)
567		}
568		paths := android.Paths{}
569		for _, root := range roots {
570			// specify relative root of file in following -f arguments
571			parArgs = append(parArgs, `-C `+root)
572			for _, path := range relativeRootMap[root] {
573				parArgs = append(parArgs, `-f `+path.String())
574				paths = append(paths, path)
575			}
576		}
577
578		origSrcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".py.srcszip")
579		ctx.Build(pctx, android.BuildParams{
580			Rule:        zip,
581			Description: "python library archive",
582			Output:      origSrcsZip,
583			// as zip rule does not use $in, there is no real need to distinguish between Inputs and Implicits
584			Implicits: paths,
585			Args: map[string]string{
586				"args": strings.Join(parArgs, " "),
587			},
588		})
589		zips = append(zips, origSrcsZip)
590	}
591	// we may have multiple zips due to separate handling of proto source files
592	if len(zips) == 1 {
593		return zips[0]
594	} else {
595		combinedSrcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".srcszip")
596		ctx.Build(pctx, android.BuildParams{
597			Rule:        combineZip,
598			Description: "combine python library archive",
599			Output:      combinedSrcsZip,
600			Inputs:      zips,
601		})
602		return combinedSrcsZip
603	}
604}
605
606func (p *PythonLibraryModule) precompileSrcs(ctx android.ModuleContext) android.Path {
607	// To precompile the python sources, we need a python interpreter and stdlib built
608	// for host. We then use those to compile the python sources, which may be used on either
609	// host of device. Python bytecode is architecture agnostic, so we're essentially
610	// "cross compiling" for device here purely by virtue of host and device python bytecode
611	// being the same.
612	var stdLib android.Path
613	var stdLibPkg string
614	var launcher android.Path
615	if proptools.BoolDefault(p.properties.Is_internal, false) {
616		stdLib = p.srcsZip
617		stdLibPkg = p.getPkgPath()
618	} else {
619		ctx.VisitDirectDepsProxyWithTag(hostStdLibTag, func(module android.ModuleProxy) {
620			if dep, ok := android.OtherModuleProvider(ctx, module, PythonLibraryInfoProvider); ok {
621				stdLib = dep.PrecompiledSrcsZip
622				stdLibPkg = dep.PkgPath
623			}
624		})
625	}
626	ctx.VisitDirectDepsProxyWithTag(hostLauncherTag, func(module android.ModuleProxy) {
627		if dep, ok := android.OtherModuleProvider(ctx, module, cc.LinkableInfoProvider); ok {
628			optionalLauncher := dep.OutputFile
629			if optionalLauncher.Valid() {
630				launcher = optionalLauncher.Path()
631			}
632		}
633	})
634	var launcherSharedLibs android.Paths
635	var ldLibraryPath []string
636	ctx.VisitDirectDepsProxyWithTag(hostlauncherSharedLibTag, func(module android.ModuleProxy) {
637		if dep, ok := android.OtherModuleProvider(ctx, module, cc.LinkableInfoProvider); ok {
638			optionalPath := dep.OutputFile
639			if optionalPath.Valid() {
640				launcherSharedLibs = append(launcherSharedLibs, optionalPath.Path())
641				ldLibraryPath = append(ldLibraryPath, filepath.Dir(optionalPath.Path().String()))
642			}
643		}
644	})
645
646	out := android.PathForModuleOut(ctx, ctx.ModuleName()+".srcszipprecompiled")
647	if stdLib == nil || launcher == nil {
648		// This shouldn't happen in a real build because we'll error out when adding dependencies
649		// on the stdlib and launcher if they don't exist. But some tests set
650		// AllowMissingDependencies.
651		return out
652	}
653	ctx.Build(pctx, android.BuildParams{
654		Rule:        precompile,
655		Input:       p.srcsZip,
656		Output:      out,
657		Implicits:   launcherSharedLibs,
658		Description: "Precompile the python sources of " + ctx.ModuleName(),
659		Args: map[string]string{
660			"stdlibZip":     stdLib.String(),
661			"stdlibPkg":     stdLibPkg,
662			"launcher":      launcher.String(),
663			"ldLibraryPath": strings.Join(ldLibraryPath, ":"),
664		},
665	})
666	return out
667}
668
669// collectPathsFromTransitiveDeps checks for source/data files for duplicate paths
670// for module and its transitive dependencies and collects list of data/source file
671// zips for transitive dependencies.
672func (p *PythonLibraryModule) collectPathsFromTransitiveDeps(ctx android.ModuleContext, precompiled bool) android.Paths {
673	// fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
674	// check duplicates.
675	destToPySrcs := make(map[string]string)
676	destToPyData := make(map[string]string)
677	for _, path := range p.srcsPathMappings {
678		destToPySrcs[path.dest] = path.src.String()
679	}
680	for _, path := range p.dataPathMappings {
681		destToPyData[path.dest] = path.src.String()
682	}
683
684	seen := make(map[android.Module]bool)
685
686	var result android.Paths
687
688	// visit all its dependencies in depth first.
689	ctx.WalkDepsProxy(func(child, _ android.ModuleProxy) bool {
690		// we only collect dependencies tagged as python library deps
691		if ctx.OtherModuleDependencyTag(child) != pythonLibTag {
692			return false
693		}
694		if seen[child] {
695			return false
696		}
697		seen[child] = true
698		// Python modules only can depend on Python libraries.
699		dep, isLibrary := android.OtherModuleProvider(ctx, child, PythonLibraryInfoProvider)
700		_, isBinary := android.OtherModuleProvider(ctx, child, PythonBinaryInfoProvider)
701		if !isLibrary || isBinary {
702			ctx.PropertyErrorf("libs",
703				"the dependency %q of module %q is not Python library!",
704				ctx.OtherModuleName(child), ctx.ModuleName())
705		}
706		// collect source and data paths, checking that there are no duplicate output file conflicts
707		if isLibrary {
708			srcs := dep.SrcsPathMappings
709			for _, path := range srcs {
710				checkForDuplicateOutputPath(ctx, destToPySrcs,
711					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
712			}
713			data := dep.DataPathMappings
714			for _, path := range data {
715				checkForDuplicateOutputPath(ctx, destToPyData,
716					path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child))
717			}
718			if precompiled {
719				result = append(result, dep.PrecompiledSrcsZip)
720			} else {
721				result = append(result, dep.SrcsZip)
722			}
723		}
724		return true
725	})
726	return result
727}
728
729func (p *PythonLibraryModule) collectSharedLibDeps(ctx android.ModuleContext) android.Paths {
730	seen := make(map[android.Module]bool)
731
732	var result android.Paths
733
734	ctx.WalkDepsProxy(func(child, _ android.ModuleProxy) bool {
735		// we only collect dependencies tagged as python library deps
736		if ctx.OtherModuleDependencyTag(child) != pythonLibTag {
737			return false
738		}
739		if seen[child] {
740			return false
741		}
742		seen[child] = true
743		dep, isLibrary := android.OtherModuleProvider(ctx, child, PythonLibraryInfoProvider)
744		if isLibrary {
745			result = append(result, dep.BundleSharedLibs...)
746		}
747		return true
748	})
749	return result
750}
751
752func (p *PythonLibraryModule) zipSharedLibs(ctx android.ModuleContext, bundleSharedLibs android.Paths) android.Path {
753	// sort the paths to keep the output deterministic
754	sort.Slice(bundleSharedLibs, func(i, j int) bool {
755		return bundleSharedLibs[i].String() < bundleSharedLibs[j].String()
756	})
757
758	parArgs := []string{"-symlinks=false", "-P lib64"}
759	paths := android.Paths{}
760	for _, path := range bundleSharedLibs {
761		// specify relative root of file in following -f arguments
762		parArgs = append(parArgs, `-C `+filepath.Dir(path.String()))
763		parArgs = append(parArgs, `-f `+path.String())
764		paths = append(paths, path)
765	}
766	srcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".sharedlibs.srcszip")
767	ctx.Build(pctx, android.BuildParams{
768		Rule:        zip,
769		Description: "bundle shared libraries for python binary",
770		Output:      srcsZip,
771		Implicits:   paths,
772		Args: map[string]string{
773			"args": strings.Join(parArgs, " "),
774		},
775	})
776	return srcsZip
777}
778
779// chckForDuplicateOutputPath checks whether outputPath has already been included in map m, which
780// would result in two files being placed in the same location.
781// If there is a duplicate path, an error is thrown and true is returned
782// Otherwise, outputPath: srcPath is added to m and returns false
783func checkForDuplicateOutputPath(ctx android.ModuleContext, m map[string]string, outputPath, srcPath, curModule, otherModule string) bool {
784	if oldSrcPath, found := m[outputPath]; found {
785		ctx.ModuleErrorf("found two files to be placed at the same location within zip %q."+
786			" First file: in module %s at path %q."+
787			" Second file: in module %s at path %q.",
788			outputPath, curModule, oldSrcPath, otherModule, srcPath)
789		return true
790	}
791	m[outputPath] = srcPath
792
793	return false
794}
795
796// InstallInData returns true as Python is not supported in the system partition
797func (p *PythonLibraryModule) InstallInData() bool {
798	return true
799}
800
801var Bool = proptools.Bool
802var BoolDefault = proptools.BoolDefault
803var String = proptools.String
804