blob: 4b4ccc286bec68c7ddf2e13778e805dce9ccd63c [file] [log] [blame]
Nan Zhangdb0b9a32017-02-27 10:12:13 -08001// 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 "io"
22 "path/filepath"
23 "strings"
24
25 "github.com/google/blueprint"
26
27 "android/soong/android"
28)
29
30func init() {
31 android.RegisterModuleType("python_binary_host", PythonBinaryHostFactory)
32}
33
34type PythonBinaryProperties struct {
35 // the name of the source file that is the main entry point of the program.
36 // this file must also be listed in srcs.
37 // If left unspecified, module name is used instead.
38 // If name doesn’t match any filename in srcs, main must be specified.
39 Main string
40
41 // set the name of the output binary.
42 Stem string
43
44 // append to the name of the output binary.
45 Suffix string
46}
47
48type PythonBinary struct {
49 pythonBaseModule
50
51 binaryProperties PythonBinaryProperties
52
53 // soong_zip arguments from all its dependencies.
54 depsParSpecs []parSpec
55
56 // Python runfiles paths from all its dependencies.
57 depsPyRunfiles []string
58
59 // the installation path for Python binary.
60 installPath android.OutputPath
61}
62
63var _ PythonSubModule = (*PythonBinary)(nil)
64
65var (
66 stubTemplateHost = "build/soong/python/scripts/stub_template_host.txt"
67)
68
69func PythonBinaryHostFactory() (blueprint.Module, []interface{}) {
70 module := &PythonBinary{}
71
72 return InitPythonBaseModule(&module.pythonBaseModule, module, android.HostSupportedNoCross,
73 &module.binaryProperties)
74}
75
76func (p *PythonBinary) GeneratePythonBuildActions(ctx android.ModuleContext) {
77 p.pythonBaseModule.GeneratePythonBuildActions(ctx)
78
79 // no Python source file for compiling par file.
80 if len(p.pythonBaseModule.srcsPathMappings) == 0 && len(p.depsPyRunfiles) == 0 {
81 return
82 }
83
84 // the runfiles packages needs to be populated with "__init__.py".
85 newPyPkgs := []string{}
86 // the set to de-duplicate the new Python packages above.
87 newPyPkgSet := make(map[string]bool)
88 // the runfiles dirs have been treated as packages.
89 existingPyPkgSet := make(map[string]bool)
90
91 wholePyRunfiles := []string{}
92 for _, path := range p.pythonBaseModule.srcsPathMappings {
93 wholePyRunfiles = append(wholePyRunfiles, path.dest)
94 }
95 wholePyRunfiles = append(wholePyRunfiles, p.depsPyRunfiles...)
96
97 // find all the runfiles dirs which have been treated as packages.
98 for _, path := range wholePyRunfiles {
99 if filepath.Base(path) != initFileName {
100 continue
101 }
102 existingPyPkg := PathBeforeLastSlash(path)
103 if _, found := existingPyPkgSet[existingPyPkg]; found {
104 panic(fmt.Errorf("found init file path duplicates: %q for module: %q.",
105 path, ctx.ModuleName()))
106 } else {
107 existingPyPkgSet[existingPyPkg] = true
108 }
109 parentPath := PathBeforeLastSlash(existingPyPkg)
110 populateNewPyPkgs(parentPath, existingPyPkgSet, newPyPkgSet, &newPyPkgs)
111 }
112
113 // create new packages under runfiles tree.
114 for _, path := range wholePyRunfiles {
115 if filepath.Base(path) == initFileName {
116 continue
117 }
118 parentPath := PathBeforeLastSlash(path)
119 populateNewPyPkgs(parentPath, existingPyPkgSet, newPyPkgSet, &newPyPkgs)
120 }
121
122 main := p.getPyMainFile(ctx)
123 if main == "" {
124 return
125 }
126 interp := p.getInterpreter(ctx)
127 if interp == "" {
128 return
129 }
130
131 // we need remove "runfiles/" suffix since stub script starts
132 // searching for main file in each sub-dir of "runfiles" directory tree.
133 binFile := registerBuildActionForParFile(ctx, p.getInterpreter(ctx),
134 strings.TrimPrefix(main, runFiles+"/"), p.getStem(ctx),
135 newPyPkgs, append(p.depsParSpecs, p.pythonBaseModule.parSpec))
136
137 // install par file.
138 p.installPath = ctx.InstallFile(
139 android.PathForModuleInstall(ctx, "bin"), binFile)
140}
141
142// get interpreter path.
143func (p *PythonBinary) getInterpreter(ctx android.ModuleContext) string {
144 var interp string
145 switch p.pythonBaseModule.properties.ActualVersion {
146 case pyVersion2:
147 interp = "python2"
148 case pyVersion3:
149 interp = "python3"
150 default:
151 panic(fmt.Errorf("unknown Python actualVersion: %q for module: %q.",
152 p.properties.ActualVersion, ctx.ModuleName()))
153 }
154
155 return interp
156}
157
158// find main program path within runfiles tree.
159func (p *PythonBinary) getPyMainFile(ctx android.ModuleContext) string {
160 var main string
161 if p.binaryProperties.Main == "" {
162 main = p.BaseModuleName() + pyExt
163 } else {
164 main = p.binaryProperties.Main
165 }
166
167 for _, path := range p.pythonBaseModule.srcsPathMappings {
168 if main == path.src.Rel() {
169 return path.dest
170 }
171 }
172 ctx.PropertyErrorf("main", "%q is not listed in srcs.", main)
173
174 return ""
175}
176
177func (p *PythonBinary) getStem(ctx android.ModuleContext) string {
178 stem := ctx.ModuleName()
179 if p.binaryProperties.Stem != "" {
180 stem = p.binaryProperties.Stem
181 }
182
183 return stem + p.binaryProperties.Suffix
184}
185
186// Sets the given directory and all its ancestor directories as Python packages.
187func populateNewPyPkgs(pkgPath string, existingPyPkgSet,
188 newPyPkgSet map[string]bool, newPyPkgs *[]string) {
189 for pkgPath != "" {
190 if _, found := existingPyPkgSet[pkgPath]; found {
191 break
192 }
193 if _, found := newPyPkgSet[pkgPath]; !found {
194 newPyPkgSet[pkgPath] = true
195 *newPyPkgs = append(*newPyPkgs, pkgPath)
196 // Gets its ancestor directory by trimming last slash.
197 pkgPath = PathBeforeLastSlash(pkgPath)
198 } else {
199 break
200 }
201 }
202}
203
204// filepath.Dir("abc") -> "." and filepath.Dir("/abc") -> "/". However,
205// the PathBeforeLastSlash() will return "" for both cases above.
206func PathBeforeLastSlash(path string) string {
207 if idx := strings.LastIndex(path, "/"); idx != -1 {
208 return path[:idx]
209 }
210 return ""
211}
212
213func (p *PythonBinary) GeneratePythonAndroidMk() (ret android.AndroidMkData, err error) {
214 // Soong installation is only supported for host modules. Have Make
215 // installation trigger Soong installation.
216 if p.pythonBaseModule.Target().Os.Class == android.Host {
217 ret.OutputFile = android.OptionalPathForPath(p.installPath)
218 }
219 ret.Class = "EXECUTABLES"
220
221 ret.Extra = append(ret.Extra, func(w io.Writer, outputFile android.Path) error {
222 path := p.installPath.RelPathString()
223 dir, file := filepath.Split(path)
224 stem := strings.TrimSuffix(file, filepath.Ext(file))
225
226 fmt.Fprintln(w, "LOCAL_MODULE_SUFFIX := "+filepath.Ext(file))
227 fmt.Fprintln(w, "LOCAL_MODULE_PATH := $(OUT_DIR)/"+filepath.Clean(dir))
228 fmt.Fprintln(w, "LOCAL_MODULE_STEM := "+stem)
229
230 return nil
231 })
232
233 return
234
235}