blob: 1c74c9af3dc680793573aa0d78f0719012d2c6e2 [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 "Base" module type for building Python program.
18
19import (
20 "fmt"
21 "path/filepath"
22 "regexp"
23 "sort"
24 "strings"
25
26 "github.com/google/blueprint"
27
28 "android/soong/android"
29)
30
31func init() {
32 android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) {
33 ctx.BottomUp("version_split", versionSplitMutator()).Parallel()
34 })
35}
36
37// the version properties that apply to python libraries and binaries.
38type PythonVersionProperties struct {
39 // true, if the module is required to be built with this version.
40 Enabled *bool
41
42 // if specified, common src files are converted to specific version with converter tool.
43 // Converter bool
44
45 // non-empty list of .py files under this strict Python version.
46 // srcs may reference the outputs of other modules that produce source files like genrule
47 // or filegroup using the syntax ":module".
48 Srcs []string
49
50 // list of the Python libraries under this Python version.
51 Libs []string
52}
53
54// properties that apply to python libraries and binaries.
55type PythonBaseModuleProperties struct {
56 // the package path prefix within the output artifact at which to place the source/data
57 // files of the current module.
58 // eg. Pkg_path = "a/b/c"; Other packages can reference this module by using
59 // (from a.b.c import ...) statement.
60 // if left unspecified, all the source/data files of current module are copied to
61 // "runfiles/" tree directory directly.
62 Pkg_path string
63
64 // list of source (.py) files compatible both with Python2 and Python3 used to compile the
65 // Python module.
66 // srcs may reference the outputs of other modules that produce source files like genrule
67 // or filegroup using the syntax ":module".
68 // Srcs has to be non-empty.
69 Srcs []string
70
71 // list of files or filegroup modules that provide data that should be installed alongside
72 // the test. the file extension can be arbitrary except for (.py).
73 Data []string
74
75 // list of the Python libraries compatible both with Python2 and Python3.
76 Libs []string
77
78 Version struct {
79 // all the "srcs" or Python dependencies that are to be used only for Python2.
80 Py2 PythonVersionProperties
81
82 // all the "srcs" or Python dependencies that are to be used only for Python3.
83 Py3 PythonVersionProperties
84 }
85
86 // the actual version each module uses after variations created.
87 // this property name is hidden from users' perspectives, and soong will populate it during
88 // runtime.
89 ActualVersion string `blueprint:"mutated"`
90}
91
92type pathMapping struct {
93 dest string
94 src android.Path
95}
96
97type pythonBaseModule struct {
98 android.ModuleBase
99 subModule PythonSubModule
100
101 properties PythonBaseModuleProperties
102
103 // the Python files of current module after expanding source dependencies.
104 // pathMapping: <dest: runfile_path, src: source_path>
105 srcsPathMappings []pathMapping
106
107 // the data files of current module after expanding source dependencies.
108 // pathMapping: <dest: runfile_path, src: source_path>
109 dataPathMappings []pathMapping
110
111 // the soong_zip arguments for zipping current module source/data files.
112 parSpec parSpec
113}
114
115type PythonSubModule interface {
116 GeneratePythonBuildActions(ctx android.ModuleContext)
117 GeneratePythonAndroidMk() (ret android.AndroidMkData, err error)
118}
119
120type PythonDependency interface {
121 GetSrcsPathMappings() []pathMapping
122 GetDataPathMappings() []pathMapping
123 GetParSpec() parSpec
124}
125
126func (p *pythonBaseModule) GetSrcsPathMappings() []pathMapping {
127 return p.srcsPathMappings
128}
129
130func (p *pythonBaseModule) GetDataPathMappings() []pathMapping {
131 return p.dataPathMappings
132}
133
134func (p *pythonBaseModule) GetParSpec() parSpec {
135 return p.parSpec
136}
137
138var _ PythonDependency = (*pythonBaseModule)(nil)
139
140var _ android.AndroidMkDataProvider = (*pythonBaseModule)(nil)
141
142func InitPythonBaseModule(baseModule *pythonBaseModule, subModule PythonSubModule,
143 hod android.HostOrDeviceSupported,
144 props ...interface{}) (blueprint.Module, []interface{}) {
145
146 baseModule.subModule = subModule
147
148 props = append(props, &baseModule.properties)
149
150 return android.InitAndroidArchModule(baseModule, hod, android.MultilibCommon, props...)
151}
152
153// the tag used to mark dependencies within "py_libs" attribute.
154type pythonDependencyTag struct {
155 blueprint.BaseDependencyTag
156}
157
158var pyDependencyTag pythonDependencyTag
159
160var (
161 pyIdentifierRegexp = regexp.MustCompile(`^([a-z]|[A-Z]|_)([a-z]|[A-Z]|[0-9]|_)*$`)
162 pyExt = ".py"
163 pyVersion2 = "PY2"
164 pyVersion3 = "PY3"
165 initFileName = "__init__.py"
166 mainFileName = "__main__.py"
167 parFileExt = ".zip"
168 runFiles = "runfiles"
169)
170
171// create version variants for modules.
172func versionSplitMutator() func(android.BottomUpMutatorContext) {
173 return func(mctx android.BottomUpMutatorContext) {
174 if base, ok := mctx.Module().(*pythonBaseModule); ok {
175 versionNames := []string{}
176 if base.properties.Version.Py2.Enabled != nil &&
177 *(base.properties.Version.Py2.Enabled) == true {
178 versionNames = append(versionNames, pyVersion2)
179 }
180 if !(base.properties.Version.Py3.Enabled != nil &&
181 *(base.properties.Version.Py3.Enabled) == false) {
182 versionNames = append(versionNames, pyVersion3)
183 }
184 modules := mctx.CreateVariations(versionNames...)
185 for i, v := range versionNames {
186 // set the actual version for Python module.
187 modules[i].(*pythonBaseModule).properties.ActualVersion = v
188 }
189 }
190 }
191}
192
193func (p *pythonBaseModule) DepsMutator(ctx android.BottomUpMutatorContext) {
194 // deps from "data".
195 android.ExtractSourcesDeps(ctx, p.properties.Data)
196 // deps from "srcs".
197 android.ExtractSourcesDeps(ctx, p.properties.Srcs)
198
199 switch p.properties.ActualVersion {
200 case pyVersion2:
201 // deps from "version.py2.srcs" property.
202 android.ExtractSourcesDeps(ctx, p.properties.Version.Py2.Srcs)
203
204 ctx.AddVariationDependencies(nil, pyDependencyTag,
205 uniqueLibs(ctx, p.properties.Libs, "version.py2.libs",
206 p.properties.Version.Py2.Libs)...)
207 case pyVersion3:
208 // deps from "version.py3.srcs" property.
209 android.ExtractSourcesDeps(ctx, p.properties.Version.Py3.Srcs)
210
211 ctx.AddVariationDependencies(nil, pyDependencyTag,
212 uniqueLibs(ctx, p.properties.Libs, "version.py3.libs",
213 p.properties.Version.Py3.Libs)...)
214 default:
215 panic(fmt.Errorf("unknown Python actualVersion: %q for module: %q.",
216 p.properties.ActualVersion, ctx.ModuleName()))
217 }
218}
219
220// check "libs" duplicates from current module dependencies.
221func uniqueLibs(ctx android.BottomUpMutatorContext,
222 commonLibs []string, versionProp string, versionLibs []string) []string {
223 set := make(map[string]string)
224 ret := []string{}
225
226 // deps from "libs" property.
227 for _, l := range commonLibs {
228 if _, found := set[l]; found {
229 ctx.PropertyErrorf("libs", "%q has duplicates within libs.", l)
230 } else {
231 set[l] = "libs"
232 ret = append(ret, l)
233 }
234 }
235 // deps from "version.pyX.libs" property.
236 for _, l := range versionLibs {
237 if _, found := set[l]; found {
238 ctx.PropertyErrorf(versionProp, "%q has duplicates within %q.", set[l])
239 } else {
240 set[l] = versionProp
241 ret = append(ret, l)
242 }
243 }
244
245 return ret
246}
247
248func (p *pythonBaseModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
249 p.subModule.GeneratePythonBuildActions(ctx)
250}
251
252func (p *pythonBaseModule) GeneratePythonBuildActions(ctx android.ModuleContext) {
253 // expand python files from "srcs" property.
254 srcs := p.properties.Srcs
255 switch p.properties.ActualVersion {
256 case pyVersion2:
257 srcs = append(srcs, p.properties.Version.Py2.Srcs...)
258 case pyVersion3:
259 srcs = append(srcs, p.properties.Version.Py3.Srcs...)
260 default:
261 panic(fmt.Errorf("unknown Python actualVersion: %q for module: %q.",
262 p.properties.ActualVersion, ctx.ModuleName()))
263 }
264 expandedSrcs := ctx.ExpandSources(srcs, nil)
265 if len(expandedSrcs) == 0 {
266 ctx.ModuleErrorf("doesn't have any source files!")
267 }
268
269 // expand data files from "data" property.
270 expandedData := ctx.ExpandSources(p.properties.Data, nil)
271
272 // sanitize pkg_path.
273 pkg_path := p.properties.Pkg_path
274 if pkg_path != "" {
275 pkg_path = filepath.Clean(p.properties.Pkg_path)
276 if pkg_path == ".." || strings.HasPrefix(pkg_path, "../") ||
277 strings.HasPrefix(pkg_path, "/") {
278 ctx.PropertyErrorf("pkg_path", "%q is not a valid format.",
279 p.properties.Pkg_path)
280 return
281 }
282 // pkg_path starts from "runfiles/" implicitly.
283 pkg_path = filepath.Join(runFiles, pkg_path)
284 } else {
285 // pkg_path starts from "runfiles/" implicitly.
286 pkg_path = runFiles
287 }
288
289 p.genModulePathMappings(ctx, pkg_path, expandedSrcs, expandedData)
290
291 p.parSpec = p.dumpFileList(ctx, pkg_path)
292
293 p.uniqWholeRunfilesTree(ctx)
294}
295
296// generate current module unique pathMappings: <dest: runfiles_path, src: source_path>
297// for python/data files.
298func (p *pythonBaseModule) genModulePathMappings(ctx android.ModuleContext, pkg_path string,
299 expandedSrcs, expandedData android.Paths) {
300 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
301 // check duplicates.
302 destToPySrcs := make(map[string]string)
303 destToPyData := make(map[string]string)
304
305 for _, s := range expandedSrcs {
306 if s.Ext() != pyExt {
307 ctx.PropertyErrorf("srcs", "found non (.py) file: %q!", s.String())
308 continue
309 }
310 runfilesPath := filepath.Join(pkg_path, s.Rel())
311 identifiers := strings.Split(strings.TrimSuffix(runfilesPath, pyExt), "/")
312 for _, token := range identifiers {
313 if !pyIdentifierRegexp.MatchString(token) {
314 ctx.PropertyErrorf("srcs", "the path %q contains invalid token %q.",
315 runfilesPath, token)
316 }
317 }
318 if fillInMap(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) {
319 p.srcsPathMappings = append(p.srcsPathMappings,
320 pathMapping{dest: runfilesPath, src: s})
321 }
322 }
323
324 for _, d := range expandedData {
325 if d.Ext() == pyExt {
326 ctx.PropertyErrorf("data", "found (.py) file: %q!", d.String())
327 continue
328 }
329 runfilesPath := filepath.Join(pkg_path, d.Rel())
330 if fillInMap(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) {
331 p.dataPathMappings = append(p.dataPathMappings,
332 pathMapping{dest: runfilesPath, src: d})
333 }
334 }
335
336}
337
338// register build actions to dump filelist to disk.
339func (p *pythonBaseModule) dumpFileList(ctx android.ModuleContext, pkg_path string) parSpec {
340 relativeRootMap := make(map[string]android.Paths)
341 // the soong_zip params in order to pack current module's Python/data files.
342 ret := parSpec{rootPrefix: pkg_path}
343
344 pathMappings := append(p.srcsPathMappings, p.dataPathMappings...)
345
346 // "srcs" or "data" properties may have filegroup so it might happen that
347 // the relative root for each source path is different.
348 for _, path := range pathMappings {
349 relativeRoot := strings.TrimSuffix(path.src.String(), path.src.Rel())
350 if v, found := relativeRootMap[relativeRoot]; found {
351 relativeRootMap[relativeRoot] = append(v, path.src)
352 } else {
353 relativeRootMap[relativeRoot] = android.Paths{path.src}
354 }
355 }
356
357 var keys []string
358
359 // in order to keep stable order of soong_zip params, we sort the keys here.
360 for k := range relativeRootMap {
361 keys = append(keys, k)
362 }
363 sort.Strings(keys)
364
365 for _, k := range keys {
366 // use relative root as filelist name.
367 fileListPath := registerBuildActionForModuleFileList(
368 ctx, strings.Replace(k, "/", "_", -1), relativeRootMap[k])
369 ret.fileListSpecs = append(ret.fileListSpecs,
370 fileListSpec{fileList: fileListPath, relativeRoot: k})
371 }
372
373 return ret
374}
375
376// check Python/data files duplicates from current module and its whole dependencies.
377func (p *pythonBaseModule) uniqWholeRunfilesTree(ctx android.ModuleContext) {
378 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
379 // check duplicates.
380 destToPySrcs := make(map[string]string)
381 destToPyData := make(map[string]string)
382
383 for _, path := range p.srcsPathMappings {
384 destToPySrcs[path.dest] = path.src.String()
385 }
386 for _, path := range p.dataPathMappings {
387 destToPyData[path.dest] = path.src.String()
388 }
389
390 // visit all its dependencies in depth first.
391 ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
392 // module can only depend on Python library.
393 if base, ok := module.(*pythonBaseModule); ok {
394 if _, ok := base.subModule.(*PythonLibrary); !ok {
395 panic(fmt.Errorf(
396 "the dependency %q of module %q is not Python library!",
397 ctx.ModuleName(), ctx.OtherModuleName(module)))
398 }
399 } else {
400 return
401 }
402 if dep, ok := module.(PythonDependency); ok {
403 srcs := dep.GetSrcsPathMappings()
404 for _, path := range srcs {
405 if !fillInMap(ctx, destToPySrcs,
406 path.dest, path.src.String(), ctx.ModuleName(),
407 ctx.OtherModuleName(module)) {
408 continue
409 }
410 // binary needs the Python runfiles paths from all its
411 // dependencies to fill __init__.py in each runfiles dir.
412 if sub, ok := p.subModule.(*PythonBinary); ok {
413 sub.depsPyRunfiles = append(sub.depsPyRunfiles, path.dest)
414 }
415 }
416 data := dep.GetDataPathMappings()
417 for _, path := range data {
418 fillInMap(ctx, destToPyData,
419 path.dest, path.src.String(), ctx.ModuleName(),
420 ctx.OtherModuleName(module))
421 }
422 // binary needs the soong_zip arguments from all its
423 // dependencies to generate executable par file.
424 if sub, ok := p.subModule.(*PythonBinary); ok {
425 sub.depsParSpecs = append(sub.depsParSpecs, dep.GetParSpec())
426 }
427 }
428 })
429}
430
431func fillInMap(ctx android.ModuleContext, m map[string]string,
432 key, value, curModule, otherModule string) bool {
433 if oldValue, found := m[key]; found {
434 ctx.ModuleErrorf("found two files to be placed at the same runfiles location %q."+
435 " First file: in module %s at path %q."+
436 " Second file: in module %s at path %q.",
437 key, curModule, oldValue, otherModule, value)
438 return false
439 } else {
440 m[key] = value
441 }
442
443 return true
444}
445
446func (p *pythonBaseModule) AndroidMk() (ret android.AndroidMkData, err error) {
447 return p.subModule.GeneratePythonAndroidMk()
448}