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