blob: df5999d8a0c2988623148104f1d1e5c6b64f4091 [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
Nan Zhang5323f8e2017-05-10 13:37:54 -0700113
114 // the installer might be nil.
115 installer installer
116
117 subAndroidMkOnce map[subAndroidMkProvider]bool
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800118}
119
120type PythonSubModule interface {
Nan Zhang5323f8e2017-05-10 13:37:54 -0700121 GeneratePythonBuildActions(ctx android.ModuleContext) android.OptionalPath
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800122}
123
124type PythonDependency interface {
125 GetSrcsPathMappings() []pathMapping
126 GetDataPathMappings() []pathMapping
127 GetParSpec() parSpec
128}
129
Nan Zhang5323f8e2017-05-10 13:37:54 -0700130type pythonDecorator struct {
131 baseInstaller *pythonInstaller
132}
133
134type installer interface {
135 install(ctx android.ModuleContext, path android.Path)
136}
137
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800138func (p *pythonBaseModule) GetSrcsPathMappings() []pathMapping {
139 return p.srcsPathMappings
140}
141
142func (p *pythonBaseModule) GetDataPathMappings() []pathMapping {
143 return p.dataPathMappings
144}
145
146func (p *pythonBaseModule) GetParSpec() parSpec {
147 return p.parSpec
148}
149
150var _ PythonDependency = (*pythonBaseModule)(nil)
151
152var _ android.AndroidMkDataProvider = (*pythonBaseModule)(nil)
153
154func InitPythonBaseModule(baseModule *pythonBaseModule, subModule PythonSubModule,
Colin Cross36242852017-06-23 15:06:31 -0700155 hod android.HostOrDeviceSupported) android.Module {
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800156
157 baseModule.subModule = subModule
158
Colin Cross36242852017-06-23 15:06:31 -0700159 baseModule.AddProperties(&baseModule.properties)
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800160
Colin Cross36242852017-06-23 15:06:31 -0700161 android.InitAndroidArchModule(baseModule, hod, android.MultilibCommon)
162
163 return baseModule
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800164}
165
166// the tag used to mark dependencies within "py_libs" attribute.
167type pythonDependencyTag struct {
168 blueprint.BaseDependencyTag
169}
170
171var pyDependencyTag pythonDependencyTag
172
173var (
174 pyIdentifierRegexp = regexp.MustCompile(`^([a-z]|[A-Z]|_)([a-z]|[A-Z]|[0-9]|_)*$`)
175 pyExt = ".py"
176 pyVersion2 = "PY2"
177 pyVersion3 = "PY3"
178 initFileName = "__init__.py"
179 mainFileName = "__main__.py"
180 parFileExt = ".zip"
181 runFiles = "runfiles"
182)
183
184// create version variants for modules.
185func versionSplitMutator() func(android.BottomUpMutatorContext) {
186 return func(mctx android.BottomUpMutatorContext) {
187 if base, ok := mctx.Module().(*pythonBaseModule); ok {
188 versionNames := []string{}
189 if base.properties.Version.Py2.Enabled != nil &&
190 *(base.properties.Version.Py2.Enabled) == true {
191 versionNames = append(versionNames, pyVersion2)
192 }
193 if !(base.properties.Version.Py3.Enabled != nil &&
194 *(base.properties.Version.Py3.Enabled) == false) {
195 versionNames = append(versionNames, pyVersion3)
196 }
197 modules := mctx.CreateVariations(versionNames...)
198 for i, v := range versionNames {
199 // set the actual version for Python module.
200 modules[i].(*pythonBaseModule).properties.ActualVersion = v
201 }
202 }
203 }
204}
205
206func (p *pythonBaseModule) DepsMutator(ctx android.BottomUpMutatorContext) {
207 // deps from "data".
208 android.ExtractSourcesDeps(ctx, p.properties.Data)
209 // deps from "srcs".
210 android.ExtractSourcesDeps(ctx, p.properties.Srcs)
211
212 switch p.properties.ActualVersion {
213 case pyVersion2:
214 // deps from "version.py2.srcs" property.
215 android.ExtractSourcesDeps(ctx, p.properties.Version.Py2.Srcs)
216
217 ctx.AddVariationDependencies(nil, pyDependencyTag,
218 uniqueLibs(ctx, p.properties.Libs, "version.py2.libs",
219 p.properties.Version.Py2.Libs)...)
220 case pyVersion3:
221 // deps from "version.py3.srcs" property.
222 android.ExtractSourcesDeps(ctx, p.properties.Version.Py3.Srcs)
223
224 ctx.AddVariationDependencies(nil, pyDependencyTag,
225 uniqueLibs(ctx, p.properties.Libs, "version.py3.libs",
226 p.properties.Version.Py3.Libs)...)
227 default:
228 panic(fmt.Errorf("unknown Python actualVersion: %q for module: %q.",
229 p.properties.ActualVersion, ctx.ModuleName()))
230 }
231}
232
233// check "libs" duplicates from current module dependencies.
234func uniqueLibs(ctx android.BottomUpMutatorContext,
235 commonLibs []string, versionProp string, versionLibs []string) []string {
236 set := make(map[string]string)
237 ret := []string{}
238
239 // deps from "libs" property.
240 for _, l := range commonLibs {
241 if _, found := set[l]; found {
242 ctx.PropertyErrorf("libs", "%q has duplicates within libs.", l)
243 } else {
244 set[l] = "libs"
245 ret = append(ret, l)
246 }
247 }
248 // deps from "version.pyX.libs" property.
249 for _, l := range versionLibs {
250 if _, found := set[l]; found {
251 ctx.PropertyErrorf(versionProp, "%q has duplicates within %q.", set[l])
252 } else {
253 set[l] = versionProp
254 ret = append(ret, l)
255 }
256 }
257
258 return ret
259}
260
261func (p *pythonBaseModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
Nan Zhang5323f8e2017-05-10 13:37:54 -0700262 installSource := p.subModule.GeneratePythonBuildActions(ctx)
263
264 if p.installer != nil && installSource.Valid() {
265 p.installer.install(ctx, installSource.Path())
266 }
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800267}
268
Nan Zhang5323f8e2017-05-10 13:37:54 -0700269func (p *pythonBaseModule) GeneratePythonBuildActions(ctx android.ModuleContext) android.OptionalPath {
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800270 // expand python files from "srcs" property.
271 srcs := p.properties.Srcs
272 switch p.properties.ActualVersion {
273 case pyVersion2:
274 srcs = append(srcs, p.properties.Version.Py2.Srcs...)
275 case pyVersion3:
276 srcs = append(srcs, p.properties.Version.Py3.Srcs...)
277 default:
278 panic(fmt.Errorf("unknown Python actualVersion: %q for module: %q.",
279 p.properties.ActualVersion, ctx.ModuleName()))
280 }
281 expandedSrcs := ctx.ExpandSources(srcs, nil)
282 if len(expandedSrcs) == 0 {
283 ctx.ModuleErrorf("doesn't have any source files!")
284 }
285
286 // expand data files from "data" property.
287 expandedData := ctx.ExpandSources(p.properties.Data, nil)
288
289 // sanitize pkg_path.
290 pkg_path := p.properties.Pkg_path
291 if pkg_path != "" {
292 pkg_path = filepath.Clean(p.properties.Pkg_path)
293 if pkg_path == ".." || strings.HasPrefix(pkg_path, "../") ||
294 strings.HasPrefix(pkg_path, "/") {
295 ctx.PropertyErrorf("pkg_path", "%q is not a valid format.",
296 p.properties.Pkg_path)
Nan Zhang5323f8e2017-05-10 13:37:54 -0700297 return android.OptionalPath{}
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800298 }
299 // pkg_path starts from "runfiles/" implicitly.
300 pkg_path = filepath.Join(runFiles, pkg_path)
301 } else {
302 // pkg_path starts from "runfiles/" implicitly.
303 pkg_path = runFiles
304 }
305
306 p.genModulePathMappings(ctx, pkg_path, expandedSrcs, expandedData)
307
308 p.parSpec = p.dumpFileList(ctx, pkg_path)
309
310 p.uniqWholeRunfilesTree(ctx)
Nan Zhang5323f8e2017-05-10 13:37:54 -0700311
312 return android.OptionalPath{}
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800313}
314
315// generate current module unique pathMappings: <dest: runfiles_path, src: source_path>
316// for python/data files.
317func (p *pythonBaseModule) genModulePathMappings(ctx android.ModuleContext, pkg_path string,
318 expandedSrcs, expandedData android.Paths) {
319 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
320 // check duplicates.
321 destToPySrcs := make(map[string]string)
322 destToPyData := make(map[string]string)
323
324 for _, s := range expandedSrcs {
325 if s.Ext() != pyExt {
326 ctx.PropertyErrorf("srcs", "found non (.py) file: %q!", s.String())
327 continue
328 }
329 runfilesPath := filepath.Join(pkg_path, s.Rel())
330 identifiers := strings.Split(strings.TrimSuffix(runfilesPath, pyExt), "/")
331 for _, token := range identifiers {
332 if !pyIdentifierRegexp.MatchString(token) {
333 ctx.PropertyErrorf("srcs", "the path %q contains invalid token %q.",
334 runfilesPath, token)
335 }
336 }
337 if fillInMap(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) {
338 p.srcsPathMappings = append(p.srcsPathMappings,
339 pathMapping{dest: runfilesPath, src: s})
340 }
341 }
342
343 for _, d := range expandedData {
344 if d.Ext() == pyExt {
345 ctx.PropertyErrorf("data", "found (.py) file: %q!", d.String())
346 continue
347 }
348 runfilesPath := filepath.Join(pkg_path, d.Rel())
349 if fillInMap(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) {
350 p.dataPathMappings = append(p.dataPathMappings,
351 pathMapping{dest: runfilesPath, src: d})
352 }
353 }
354
355}
356
357// register build actions to dump filelist to disk.
358func (p *pythonBaseModule) dumpFileList(ctx android.ModuleContext, pkg_path string) parSpec {
359 relativeRootMap := make(map[string]android.Paths)
360 // the soong_zip params in order to pack current module's Python/data files.
361 ret := parSpec{rootPrefix: pkg_path}
362
363 pathMappings := append(p.srcsPathMappings, p.dataPathMappings...)
364
365 // "srcs" or "data" properties may have filegroup so it might happen that
366 // the relative root for each source path is different.
367 for _, path := range pathMappings {
368 relativeRoot := strings.TrimSuffix(path.src.String(), path.src.Rel())
369 if v, found := relativeRootMap[relativeRoot]; found {
370 relativeRootMap[relativeRoot] = append(v, path.src)
371 } else {
372 relativeRootMap[relativeRoot] = android.Paths{path.src}
373 }
374 }
375
376 var keys []string
377
378 // in order to keep stable order of soong_zip params, we sort the keys here.
379 for k := range relativeRootMap {
380 keys = append(keys, k)
381 }
382 sort.Strings(keys)
383
384 for _, k := range keys {
385 // use relative root as filelist name.
386 fileListPath := registerBuildActionForModuleFileList(
387 ctx, strings.Replace(k, "/", "_", -1), relativeRootMap[k])
388 ret.fileListSpecs = append(ret.fileListSpecs,
389 fileListSpec{fileList: fileListPath, relativeRoot: k})
390 }
391
392 return ret
393}
394
395// check Python/data files duplicates from current module and its whole dependencies.
396func (p *pythonBaseModule) uniqWholeRunfilesTree(ctx android.ModuleContext) {
397 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to
398 // check duplicates.
399 destToPySrcs := make(map[string]string)
400 destToPyData := make(map[string]string)
401
402 for _, path := range p.srcsPathMappings {
403 destToPySrcs[path.dest] = path.src.String()
404 }
405 for _, path := range p.dataPathMappings {
406 destToPyData[path.dest] = path.src.String()
407 }
408
409 // visit all its dependencies in depth first.
410 ctx.VisitDepsDepthFirst(func(module blueprint.Module) {
411 // module can only depend on Python library.
412 if base, ok := module.(*pythonBaseModule); ok {
413 if _, ok := base.subModule.(*PythonLibrary); !ok {
414 panic(fmt.Errorf(
415 "the dependency %q of module %q is not Python library!",
416 ctx.ModuleName(), ctx.OtherModuleName(module)))
417 }
418 } else {
419 return
420 }
421 if dep, ok := module.(PythonDependency); ok {
422 srcs := dep.GetSrcsPathMappings()
423 for _, path := range srcs {
424 if !fillInMap(ctx, destToPySrcs,
425 path.dest, path.src.String(), ctx.ModuleName(),
426 ctx.OtherModuleName(module)) {
427 continue
428 }
429 // binary needs the Python runfiles paths from all its
430 // dependencies to fill __init__.py in each runfiles dir.
Nan Zhang5323f8e2017-05-10 13:37:54 -0700431 if sub, ok := p.subModule.(*pythonBinaryBase); ok {
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800432 sub.depsPyRunfiles = append(sub.depsPyRunfiles, path.dest)
433 }
434 }
435 data := dep.GetDataPathMappings()
436 for _, path := range data {
437 fillInMap(ctx, destToPyData,
438 path.dest, path.src.String(), ctx.ModuleName(),
439 ctx.OtherModuleName(module))
440 }
441 // binary needs the soong_zip arguments from all its
442 // dependencies to generate executable par file.
Nan Zhang5323f8e2017-05-10 13:37:54 -0700443 if sub, ok := p.subModule.(*pythonBinaryBase); ok {
Nan Zhangdb0b9a32017-02-27 10:12:13 -0800444 sub.depsParSpecs = append(sub.depsParSpecs, dep.GetParSpec())
445 }
446 }
447 })
448}
449
450func fillInMap(ctx android.ModuleContext, m map[string]string,
451 key, value, curModule, otherModule string) bool {
452 if oldValue, found := m[key]; found {
453 ctx.ModuleErrorf("found two files to be placed at the same runfiles location %q."+
454 " First file: in module %s at path %q."+
455 " Second file: in module %s at path %q.",
456 key, curModule, oldValue, otherModule, value)
457 return false
458 } else {
459 m[key] = value
460 }
461
462 return true
463}