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 module types for building Python binary.
18
19import (
20	"fmt"
21	"path/filepath"
22	"strings"
23
24	"android/soong/android"
25)
26
27func init() {
28	registerPythonBinaryComponents(android.InitRegistrationContext)
29}
30
31func registerPythonBinaryComponents(ctx android.RegistrationContext) {
32	ctx.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
33}
34
35type BinaryProperties struct {
36	// the name of the source file that is the main entry point of the program.
37	// this file must also be listed in srcs.
38	// If left unspecified, module name is used instead.
39	// If name doesn’t match any filename in srcs, main must be specified.
40	Main *string
41
42	// set the name of the output binary.
43	Stem *string `android:"arch_variant"`
44
45	// append to the name of the output binary.
46	Suffix *string `android:"arch_variant"`
47
48	// list of compatibility suites (for example "cts", "vts") that the module should be
49	// installed into.
50	Test_suites []string `android:"arch_variant"`
51
52	// whether to use `main` when starting the executable. The default is true, when set to
53	// false it will act much like the normal `python` executable, but with the sources and
54	// libraries automatically included in the PYTHONPATH.
55	Autorun *bool `android:"arch_variant"`
56
57	// Flag to indicate whether or not to create test config automatically. If AndroidTest.xml
58	// doesn't exist next to the Android.bp, this attribute doesn't need to be set to true
59	// explicitly.
60	Auto_gen_config *bool
61}
62
63type PythonBinaryModule struct {
64	PythonLibraryModule
65	binaryProperties BinaryProperties
66
67	// (.intermediate) module output path as installation source.
68	installSource android.Path
69
70	// Final installation path.
71	installedDest android.Path
72
73	androidMkSharedLibs []string
74}
75
76var _ android.AndroidMkEntriesProvider = (*PythonBinaryModule)(nil)
77var _ android.Module = (*PythonBinaryModule)(nil)
78
79type IntermPathProvider interface {
80	IntermPathForModuleOut() android.OptionalPath
81}
82
83func NewBinary(hod android.HostOrDeviceSupported) *PythonBinaryModule {
84	return &PythonBinaryModule{
85		PythonLibraryModule: *newModule(hod, android.MultilibFirst),
86	}
87}
88
89func PythonBinaryHostFactory() android.Module {
90	return NewBinary(android.HostSupported).init()
91}
92
93func (p *PythonBinaryModule) init() android.Module {
94	p.AddProperties(&p.properties, &p.protoProperties)
95	p.AddProperties(&p.binaryProperties)
96	android.InitAndroidArchModule(p, p.hod, p.multilib)
97	android.InitDefaultableModule(p)
98	return p
99}
100
101func (p *PythonBinaryModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
102	p.PythonLibraryModule.GenerateAndroidBuildActions(ctx)
103	p.buildBinary(ctx)
104	p.installedDest = ctx.InstallFile(installDir(ctx, "bin", "", ""),
105		p.installSource.Base(), p.installSource)
106}
107
108func (p *PythonBinaryModule) buildBinary(ctx android.ModuleContext) {
109	embeddedLauncher := p.isEmbeddedLauncherEnabled()
110	depsSrcsZips := p.collectPathsFromTransitiveDeps(ctx, embeddedLauncher)
111	main := ""
112	if p.autorun() {
113		main = p.getPyMainFile(ctx, p.srcsPathMappings)
114	}
115
116	var launcherPath android.OptionalPath
117	if embeddedLauncher {
118		ctx.VisitDirectDepsWithTag(launcherTag, func(m android.Module) {
119			if provider, ok := m.(IntermPathProvider); ok {
120				if launcherPath.Valid() {
121					panic(fmt.Errorf("launcher path was found before: %q",
122						launcherPath))
123				}
124				launcherPath = provider.IntermPathForModuleOut()
125			}
126		})
127	}
128	srcsZips := make(android.Paths, 0, len(depsSrcsZips)+1)
129	if embeddedLauncher {
130		srcsZips = append(srcsZips, p.precompiledSrcsZip)
131	} else {
132		srcsZips = append(srcsZips, p.srcsZip)
133	}
134	srcsZips = append(srcsZips, depsSrcsZips...)
135	p.installSource = registerBuildActionForParFile(ctx, embeddedLauncher, launcherPath,
136		p.getHostInterpreterName(ctx, p.properties.Actual_version),
137		main, p.getStem(ctx), srcsZips)
138
139	var sharedLibs []string
140	// if embedded launcher is enabled, we need to collect the shared library dependencies of the
141	// launcher
142	for _, dep := range ctx.GetDirectDepsWithTag(launcherSharedLibTag) {
143		sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep))
144	}
145	p.androidMkSharedLibs = sharedLibs
146}
147
148func (p *PythonBinaryModule) AndroidMkEntries() []android.AndroidMkEntries {
149	entries := android.AndroidMkEntries{OutputFile: android.OptionalPathForPath(p.installSource)}
150
151	entries.Class = "EXECUTABLES"
152
153	entries.ExtraEntries = append(entries.ExtraEntries,
154		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
155			entries.AddCompatibilityTestSuites(p.binaryProperties.Test_suites...)
156		})
157
158	entries.Required = append(entries.Required, "libc++")
159	entries.ExtraEntries = append(entries.ExtraEntries,
160		func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
161			path, file := filepath.Split(p.installedDest.String())
162			stem := strings.TrimSuffix(file, filepath.Ext(file))
163
164			entries.SetString("LOCAL_MODULE_SUFFIX", filepath.Ext(file))
165			entries.SetString("LOCAL_MODULE_PATH", path)
166			entries.SetString("LOCAL_MODULE_STEM", stem)
167			entries.AddStrings("LOCAL_SHARED_LIBRARIES", p.androidMkSharedLibs...)
168			entries.SetBool("LOCAL_CHECK_ELF_FILES", false)
169		})
170
171	return []android.AndroidMkEntries{entries}
172}
173
174func (p *PythonBinaryModule) DepsMutator(ctx android.BottomUpMutatorContext) {
175	p.PythonLibraryModule.DepsMutator(ctx)
176
177	if p.isEmbeddedLauncherEnabled() {
178		p.AddDepsOnPythonLauncherAndStdlib(ctx, pythonLibTag, launcherTag, launcherSharedLibTag, p.autorun(), ctx.Target())
179	}
180}
181
182// HostToolPath returns a path if appropriate such that this module can be used as a host tool,
183// fulfilling the android.HostToolProvider interface.
184func (p *PythonBinaryModule) HostToolPath() android.OptionalPath {
185	// TODO: This should only be set when building host binaries -- tests built for device would be
186	// setting this incorrectly.
187	return android.OptionalPathForPath(p.installedDest)
188}
189
190// OutputFiles returns output files based on given tag, returns an error if tag is unsupported.
191func (p *PythonBinaryModule) OutputFiles(tag string) (android.Paths, error) {
192	switch tag {
193	case "":
194		return android.Paths{p.installSource}, nil
195	default:
196		return nil, fmt.Errorf("unsupported module reference tag %q", tag)
197	}
198}
199
200func (p *PythonBinaryModule) isEmbeddedLauncherEnabled() bool {
201	return BoolDefault(p.properties.Embedded_launcher, true)
202}
203
204func (b *PythonBinaryModule) autorun() bool {
205	return BoolDefault(b.binaryProperties.Autorun, true)
206}
207
208// get host interpreter name.
209func (p *PythonBinaryModule) getHostInterpreterName(ctx android.ModuleContext,
210	actualVersion string) string {
211	var interp string
212	switch actualVersion {
213	case pyVersion2:
214		interp = "python2.7"
215	case pyVersion3:
216		interp = "python3"
217	default:
218		panic(fmt.Errorf("unknown Python actualVersion: %q for module: %q.",
219			actualVersion, ctx.ModuleName()))
220	}
221
222	return interp
223}
224
225// find main program path within runfiles tree.
226func (p *PythonBinaryModule) getPyMainFile(ctx android.ModuleContext,
227	srcsPathMappings []pathMapping) string {
228	var main string
229	if String(p.binaryProperties.Main) == "" {
230		main = ctx.ModuleName() + pyExt
231	} else {
232		main = String(p.binaryProperties.Main)
233	}
234
235	for _, path := range srcsPathMappings {
236		if main == path.src.Rel() {
237			return path.dest
238		}
239	}
240	ctx.PropertyErrorf("main", "%q is not listed in srcs.", main)
241
242	return ""
243}
244
245func (p *PythonBinaryModule) getStem(ctx android.ModuleContext) string {
246	stem := ctx.ModuleName()
247	if String(p.binaryProperties.Stem) != "" {
248		stem = String(p.binaryProperties.Stem)
249	}
250
251	return stem + String(p.binaryProperties.Suffix)
252}
253
254func installDir(ctx android.ModuleContext, dir, dir64, relative string) android.InstallPath {
255	if ctx.Arch().ArchType.Multilib == "lib64" && dir64 != "" {
256		dir = dir64
257	}
258	if !ctx.Host() && ctx.Config().HasMultilibConflict(ctx.Arch().ArchType) {
259		dir = filepath.Join(dir, ctx.Arch().ArchType.String())
260	}
261	return android.PathForModuleInstall(ctx, dir, relative)
262}
263