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 "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 "github.com/google/blueprint/proptools" 28 29 "android/soong/android" 30) 31 32func init() { 33 android.PreDepsMutators(func(ctx android.RegisterMutatorsContext) { 34 ctx.BottomUp("version_split", versionSplitMutator()).Parallel() 35 }) 36} 37 38// the version properties that apply to python libraries and binaries. 39type VersionProperties struct { 40 // true, if the module is required to be built with this version. 41 Enabled *bool `android:"arch_variant"` 42 43 // non-empty list of .py files under this strict Python version. 44 // srcs may reference the outputs of other modules that produce source files like genrule 45 // or filegroup using the syntax ":module". 46 Srcs []string `android:"path,arch_variant"` 47 48 // list of source files that should not be used to build the Python module. 49 // This is most useful in the arch/multilib variants to remove non-common files 50 Exclude_srcs []string `android:"path,arch_variant"` 51 52 // list of the Python libraries under this Python version. 53 Libs []string `android:"arch_variant"` 54 55 // true, if the binary is required to be built with embedded launcher. 56 // TODO(nanzhang): Remove this flag when embedded Python3 is supported later. 57 Embedded_launcher *bool `android:"arch_variant"` 58} 59 60// properties that apply to python libraries and binaries. 61type BaseProperties struct { 62 // the package path prefix within the output artifact at which to place the source/data 63 // files of the current module. 64 // eg. Pkg_path = "a/b/c"; Other packages can reference this module by using 65 // (from a.b.c import ...) statement. 66 // if left unspecified, all the source/data files path is unchanged within zip file. 67 Pkg_path *string `android:"arch_variant"` 68 69 // true, if the Python module is used internally, eg, Python std libs. 70 Is_internal *bool `android:"arch_variant"` 71 72 // list of source (.py) files compatible both with Python2 and Python3 used to compile the 73 // Python module. 74 // srcs may reference the outputs of other modules that produce source files like genrule 75 // or filegroup using the syntax ":module". 76 // Srcs has to be non-empty. 77 Srcs []string `android:"path,arch_variant"` 78 79 // list of source files that should not be used to build the C/C++ module. 80 // This is most useful in the arch/multilib variants to remove non-common files 81 Exclude_srcs []string `android:"path,arch_variant"` 82 83 // list of files or filegroup modules that provide data that should be installed alongside 84 // the test. the file extension can be arbitrary except for (.py). 85 Data []string `android:"path,arch_variant"` 86 87 // list of the Python libraries compatible both with Python2 and Python3. 88 Libs []string `android:"arch_variant"` 89 90 Version struct { 91 // all the "srcs" or Python dependencies that are to be used only for Python2. 92 Py2 VersionProperties `android:"arch_variant"` 93 94 // all the "srcs" or Python dependencies that are to be used only for Python3. 95 Py3 VersionProperties `android:"arch_variant"` 96 } `android:"arch_variant"` 97 98 // the actual version each module uses after variations created. 99 // this property name is hidden from users' perspectives, and soong will populate it during 100 // runtime. 101 Actual_version string `blueprint:"mutated"` 102} 103 104type pathMapping struct { 105 dest string 106 src android.Path 107} 108 109type Module struct { 110 android.ModuleBase 111 android.DefaultableModuleBase 112 113 properties BaseProperties 114 protoProperties android.ProtoProperties 115 116 // initialize before calling Init 117 hod android.HostOrDeviceSupported 118 multilib android.Multilib 119 120 // the bootstrapper is used to bootstrap .par executable. 121 // bootstrapper might be nil (Python library module). 122 bootstrapper bootstrapper 123 124 // the installer might be nil. 125 installer installer 126 127 // the Python files of current module after expanding source dependencies. 128 // pathMapping: <dest: runfile_path, src: source_path> 129 srcsPathMappings []pathMapping 130 131 // the data files of current module after expanding source dependencies. 132 // pathMapping: <dest: runfile_path, src: source_path> 133 dataPathMappings []pathMapping 134 135 // the zip filepath for zipping current module source/data files. 136 srcsZip android.Path 137 138 // dependency modules' zip filepath for zipping current module source/data files. 139 depsSrcsZips android.Paths 140 141 // (.intermediate) module output path as installation source. 142 installSource android.OptionalPath 143 144 subAndroidMkOnce map[subAndroidMkProvider]bool 145} 146 147func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module { 148 return &Module{ 149 hod: hod, 150 multilib: multilib, 151 } 152} 153 154type bootstrapper interface { 155 bootstrapperProps() []interface{} 156 bootstrap(ctx android.ModuleContext, ActualVersion string, embeddedLauncher bool, 157 srcsPathMappings []pathMapping, srcsZip android.Path, 158 depsSrcsZips android.Paths) android.OptionalPath 159 160 autorun() bool 161} 162 163type installer interface { 164 install(ctx android.ModuleContext, path android.Path) 165 setAndroidMkSharedLibs(sharedLibs []string) 166} 167 168type PythonDependency interface { 169 GetSrcsPathMappings() []pathMapping 170 GetDataPathMappings() []pathMapping 171 GetSrcsZip() android.Path 172} 173 174func (p *Module) GetSrcsPathMappings() []pathMapping { 175 return p.srcsPathMappings 176} 177 178func (p *Module) GetDataPathMappings() []pathMapping { 179 return p.dataPathMappings 180} 181 182func (p *Module) GetSrcsZip() android.Path { 183 return p.srcsZip 184} 185 186var _ PythonDependency = (*Module)(nil) 187 188var _ android.AndroidMkDataProvider = (*Module)(nil) 189 190func (p *Module) Init() android.Module { 191 192 p.AddProperties(&p.properties, &p.protoProperties) 193 if p.bootstrapper != nil { 194 p.AddProperties(p.bootstrapper.bootstrapperProps()...) 195 } 196 197 android.InitAndroidArchModule(p, p.hod, p.multilib) 198 android.InitDefaultableModule(p) 199 200 return p 201} 202 203type dependencyTag struct { 204 blueprint.BaseDependencyTag 205 name string 206} 207 208var ( 209 pythonLibTag = dependencyTag{name: "pythonLib"} 210 launcherTag = dependencyTag{name: "launcher"} 211 launcherSharedLibTag = dependencyTag{name: "launcherSharedLib"} 212 pyIdentifierRegexp = regexp.MustCompile(`^[a-zA-Z_][a-zA-Z0-9_-]*$`) 213 pyExt = ".py" 214 protoExt = ".proto" 215 pyVersion2 = "PY2" 216 pyVersion3 = "PY3" 217 initFileName = "__init__.py" 218 mainFileName = "__main__.py" 219 entryPointFile = "entry_point.txt" 220 parFileExt = ".zip" 221 internal = "internal" 222) 223 224// create version variants for modules. 225func versionSplitMutator() func(android.BottomUpMutatorContext) { 226 return func(mctx android.BottomUpMutatorContext) { 227 if base, ok := mctx.Module().(*Module); ok { 228 versionNames := []string{} 229 if base.properties.Version.Py2.Enabled != nil && 230 *(base.properties.Version.Py2.Enabled) == true { 231 versionNames = append(versionNames, pyVersion2) 232 } 233 if !(base.properties.Version.Py3.Enabled != nil && 234 *(base.properties.Version.Py3.Enabled) == false) { 235 versionNames = append(versionNames, pyVersion3) 236 } 237 modules := mctx.CreateVariations(versionNames...) 238 for i, v := range versionNames { 239 // set the actual version for Python module. 240 modules[i].(*Module).properties.Actual_version = v 241 } 242 } 243 } 244} 245 246func (p *Module) HostToolPath() android.OptionalPath { 247 if p.installer == nil { 248 // python_library is just meta module, and doesn't have any installer. 249 return android.OptionalPath{} 250 } 251 return android.OptionalPathForPath(p.installer.(*binaryDecorator).path) 252} 253 254func (p *Module) isEmbeddedLauncherEnabled(actual_version string) bool { 255 switch actual_version { 256 case pyVersion2: 257 return Bool(p.properties.Version.Py2.Embedded_launcher) 258 case pyVersion3: 259 return Bool(p.properties.Version.Py3.Embedded_launcher) 260 } 261 262 return false 263} 264 265func hasSrcExt(srcs []string, ext string) bool { 266 for _, src := range srcs { 267 if filepath.Ext(src) == ext { 268 return true 269 } 270 } 271 272 return false 273} 274 275func (p *Module) hasSrcExt(ctx android.BottomUpMutatorContext, ext string) bool { 276 if hasSrcExt(p.properties.Srcs, protoExt) { 277 return true 278 } 279 switch p.properties.Actual_version { 280 case pyVersion2: 281 return hasSrcExt(p.properties.Version.Py2.Srcs, protoExt) 282 case pyVersion3: 283 return hasSrcExt(p.properties.Version.Py3.Srcs, protoExt) 284 default: 285 panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.", 286 p.properties.Actual_version, ctx.ModuleName())) 287 } 288} 289 290func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) { 291 android.ProtoDeps(ctx, &p.protoProperties) 292 293 if p.hasSrcExt(ctx, protoExt) && p.Name() != "libprotobuf-python" { 294 ctx.AddVariationDependencies(nil, pythonLibTag, "libprotobuf-python") 295 } 296 switch p.properties.Actual_version { 297 case pyVersion2: 298 ctx.AddVariationDependencies(nil, pythonLibTag, 299 uniqueLibs(ctx, p.properties.Libs, "version.py2.libs", 300 p.properties.Version.Py2.Libs)...) 301 302 if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled(pyVersion2) { 303 ctx.AddVariationDependencies(nil, pythonLibTag, "py2-stdlib") 304 305 launcherModule := "py2-launcher" 306 if p.bootstrapper.autorun() { 307 launcherModule = "py2-launcher-autorun" 308 } 309 ctx.AddFarVariationDependencies([]blueprint.Variation{ 310 {Mutator: "arch", Variation: ctx.Target().String()}, 311 }, launcherTag, launcherModule) 312 313 // Add py2-launcher shared lib dependencies. Ideally, these should be 314 // derived from the `shared_libs` property of "py2-launcher". However, we 315 // cannot read the property at this stage and it will be too late to add 316 // dependencies later. 317 ctx.AddFarVariationDependencies([]blueprint.Variation{ 318 {Mutator: "arch", Variation: ctx.Target().String()}, 319 }, launcherSharedLibTag, "libsqlite") 320 321 if ctx.Target().Os.Bionic() { 322 ctx.AddFarVariationDependencies([]blueprint.Variation{ 323 {Mutator: "arch", Variation: ctx.Target().String()}, 324 }, launcherSharedLibTag, "libc", "libdl", "libm") 325 } 326 } 327 328 case pyVersion3: 329 ctx.AddVariationDependencies(nil, pythonLibTag, 330 uniqueLibs(ctx, p.properties.Libs, "version.py3.libs", 331 p.properties.Version.Py3.Libs)...) 332 333 if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled(pyVersion3) { 334 //TODO(nanzhang): Add embedded launcher for Python3. 335 ctx.PropertyErrorf("version.py3.embedded_launcher", 336 "is not supported yet for Python3.") 337 } 338 default: 339 panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.", 340 p.properties.Actual_version, ctx.ModuleName())) 341 } 342} 343 344// check "libs" duplicates from current module dependencies. 345func uniqueLibs(ctx android.BottomUpMutatorContext, 346 commonLibs []string, versionProp string, versionLibs []string) []string { 347 set := make(map[string]string) 348 ret := []string{} 349 350 // deps from "libs" property. 351 for _, l := range commonLibs { 352 if _, found := set[l]; found { 353 ctx.PropertyErrorf("libs", "%q has duplicates within libs.", l) 354 } else { 355 set[l] = "libs" 356 ret = append(ret, l) 357 } 358 } 359 // deps from "version.pyX.libs" property. 360 for _, l := range versionLibs { 361 if _, found := set[l]; found { 362 ctx.PropertyErrorf(versionProp, "%q has duplicates within %q.", set[l]) 363 } else { 364 set[l] = versionProp 365 ret = append(ret, l) 366 } 367 } 368 369 return ret 370} 371 372func (p *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) { 373 p.GeneratePythonBuildActions(ctx) 374 375 // Only Python binaries and test has non-empty bootstrapper. 376 if p.bootstrapper != nil { 377 p.walkTransitiveDeps(ctx) 378 // TODO(nanzhang): Since embedded launcher is not supported for Python3 for now, 379 // so we initialize "embedded_launcher" to false. 380 embeddedLauncher := false 381 if p.properties.Actual_version == pyVersion2 { 382 embeddedLauncher = p.isEmbeddedLauncherEnabled(pyVersion2) 383 } 384 p.installSource = p.bootstrapper.bootstrap(ctx, p.properties.Actual_version, 385 embeddedLauncher, p.srcsPathMappings, p.srcsZip, p.depsSrcsZips) 386 } 387 388 if p.installer != nil { 389 var sharedLibs []string 390 ctx.VisitDirectDeps(func(dep android.Module) { 391 if ctx.OtherModuleDependencyTag(dep) == launcherSharedLibTag { 392 sharedLibs = append(sharedLibs, ctx.OtherModuleName(dep)) 393 } 394 }) 395 p.installer.setAndroidMkSharedLibs(sharedLibs) 396 397 if p.installSource.Valid() { 398 p.installer.install(ctx, p.installSource.Path()) 399 } 400 } 401 402} 403 404func (p *Module) GeneratePythonBuildActions(ctx android.ModuleContext) { 405 // expand python files from "srcs" property. 406 srcs := p.properties.Srcs 407 exclude_srcs := p.properties.Exclude_srcs 408 switch p.properties.Actual_version { 409 case pyVersion2: 410 srcs = append(srcs, p.properties.Version.Py2.Srcs...) 411 exclude_srcs = append(exclude_srcs, p.properties.Version.Py2.Exclude_srcs...) 412 case pyVersion3: 413 srcs = append(srcs, p.properties.Version.Py3.Srcs...) 414 exclude_srcs = append(exclude_srcs, p.properties.Version.Py3.Exclude_srcs...) 415 default: 416 panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.", 417 p.properties.Actual_version, ctx.ModuleName())) 418 } 419 expandedSrcs := android.PathsForModuleSrcExcludes(ctx, srcs, exclude_srcs) 420 requiresSrcs := true 421 if p.bootstrapper != nil && !p.bootstrapper.autorun() { 422 requiresSrcs = false 423 } 424 if len(expandedSrcs) == 0 && requiresSrcs { 425 ctx.ModuleErrorf("doesn't have any source files!") 426 } 427 428 // expand data files from "data" property. 429 expandedData := android.PathsForModuleSrc(ctx, p.properties.Data) 430 431 // sanitize pkg_path. 432 pkgPath := String(p.properties.Pkg_path) 433 if pkgPath != "" { 434 pkgPath = filepath.Clean(String(p.properties.Pkg_path)) 435 if pkgPath == ".." || strings.HasPrefix(pkgPath, "../") || 436 strings.HasPrefix(pkgPath, "/") { 437 ctx.PropertyErrorf("pkg_path", 438 "%q must be a relative path contained in par file.", 439 String(p.properties.Pkg_path)) 440 return 441 } 442 if p.properties.Is_internal != nil && *p.properties.Is_internal { 443 pkgPath = filepath.Join(internal, pkgPath) 444 } 445 } else { 446 if p.properties.Is_internal != nil && *p.properties.Is_internal { 447 pkgPath = internal 448 } 449 } 450 451 p.genModulePathMappings(ctx, pkgPath, expandedSrcs, expandedData) 452 453 p.srcsZip = p.createSrcsZip(ctx, pkgPath) 454} 455 456// generate current module unique pathMappings: <dest: runfiles_path, src: source_path> 457// for python/data files. 458func (p *Module) genModulePathMappings(ctx android.ModuleContext, pkgPath string, 459 expandedSrcs, expandedData android.Paths) { 460 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to 461 // check current module duplicates. 462 destToPySrcs := make(map[string]string) 463 destToPyData := make(map[string]string) 464 465 for _, s := range expandedSrcs { 466 if s.Ext() != pyExt && s.Ext() != protoExt { 467 ctx.PropertyErrorf("srcs", "found non (.py|.proto) file: %q!", s.String()) 468 continue 469 } 470 runfilesPath := filepath.Join(pkgPath, s.Rel()) 471 identifiers := strings.Split(strings.TrimSuffix(runfilesPath, 472 filepath.Ext(runfilesPath)), "/") 473 for _, token := range identifiers { 474 if !pyIdentifierRegexp.MatchString(token) { 475 ctx.PropertyErrorf("srcs", "the path %q contains invalid token %q.", 476 runfilesPath, token) 477 } 478 } 479 if fillInMap(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) { 480 p.srcsPathMappings = append(p.srcsPathMappings, 481 pathMapping{dest: runfilesPath, src: s}) 482 } 483 } 484 485 for _, d := range expandedData { 486 if d.Ext() == pyExt || d.Ext() == protoExt { 487 ctx.PropertyErrorf("data", "found (.py|.proto) file: %q!", d.String()) 488 continue 489 } 490 runfilesPath := filepath.Join(pkgPath, d.Rel()) 491 if fillInMap(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) { 492 p.dataPathMappings = append(p.dataPathMappings, 493 pathMapping{dest: runfilesPath, src: d}) 494 } 495 } 496} 497 498// register build actions to zip current module's sources. 499func (p *Module) createSrcsZip(ctx android.ModuleContext, pkgPath string) android.Path { 500 relativeRootMap := make(map[string]android.Paths) 501 pathMappings := append(p.srcsPathMappings, p.dataPathMappings...) 502 503 var protoSrcs android.Paths 504 // "srcs" or "data" properties may have filegroup so it might happen that 505 // the relative root for each source path is different. 506 for _, path := range pathMappings { 507 if path.src.Ext() == protoExt { 508 protoSrcs = append(protoSrcs, path.src) 509 } else { 510 var relativeRoot string 511 relativeRoot = strings.TrimSuffix(path.src.String(), path.src.Rel()) 512 if v, found := relativeRootMap[relativeRoot]; found { 513 relativeRootMap[relativeRoot] = append(v, path.src) 514 } else { 515 relativeRootMap[relativeRoot] = android.Paths{path.src} 516 } 517 } 518 } 519 var zips android.Paths 520 if len(protoSrcs) > 0 { 521 protoFlags := android.GetProtoFlags(ctx, &p.protoProperties) 522 protoFlags.OutTypeFlag = "--python_out" 523 524 for _, srcFile := range protoSrcs { 525 zip := genProto(ctx, srcFile, protoFlags, pkgPath) 526 zips = append(zips, zip) 527 } 528 } 529 530 if len(relativeRootMap) > 0 { 531 var keys []string 532 533 // in order to keep stable order of soong_zip params, we sort the keys here. 534 for k := range relativeRootMap { 535 keys = append(keys, k) 536 } 537 sort.Strings(keys) 538 539 parArgs := []string{} 540 if pkgPath != "" { 541 parArgs = append(parArgs, `-P `+pkgPath) 542 } 543 implicits := android.Paths{} 544 for _, k := range keys { 545 parArgs = append(parArgs, `-C `+k) 546 for _, path := range relativeRootMap[k] { 547 parArgs = append(parArgs, `-f `+path.String()) 548 implicits = append(implicits, path) 549 } 550 } 551 552 origSrcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".py.srcszip") 553 ctx.Build(pctx, android.BuildParams{ 554 Rule: zip, 555 Description: "python library archive", 556 Output: origSrcsZip, 557 Implicits: implicits, 558 Args: map[string]string{ 559 "args": strings.Join(parArgs, " "), 560 }, 561 }) 562 zips = append(zips, origSrcsZip) 563 } 564 if len(zips) == 1 { 565 return zips[0] 566 } else { 567 combinedSrcsZip := android.PathForModuleOut(ctx, ctx.ModuleName()+".srcszip") 568 ctx.Build(pctx, android.BuildParams{ 569 Rule: combineZip, 570 Description: "combine python library archive", 571 Output: combinedSrcsZip, 572 Inputs: zips, 573 }) 574 return combinedSrcsZip 575 } 576} 577 578func isPythonLibModule(module blueprint.Module) bool { 579 if m, ok := module.(*Module); ok { 580 // Python library has no bootstrapper or installer. 581 if m.bootstrapper != nil || m.installer != nil { 582 return false 583 } 584 return true 585 } 586 return false 587} 588 589// check Python source/data files duplicates for whole runfiles tree since Python binary/test 590// need collect and zip all srcs of whole transitive dependencies to a final par file. 591func (p *Module) walkTransitiveDeps(ctx android.ModuleContext) { 592 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to 593 // check duplicates. 594 destToPySrcs := make(map[string]string) 595 destToPyData := make(map[string]string) 596 597 for _, path := range p.srcsPathMappings { 598 destToPySrcs[path.dest] = path.src.String() 599 } 600 for _, path := range p.dataPathMappings { 601 destToPyData[path.dest] = path.src.String() 602 } 603 604 seen := make(map[android.Module]bool) 605 606 // visit all its dependencies in depth first. 607 ctx.WalkDeps(func(child, parent android.Module) bool { 608 if ctx.OtherModuleDependencyTag(child) != pythonLibTag { 609 return false 610 } 611 if seen[child] { 612 return false 613 } 614 seen[child] = true 615 // Python modules only can depend on Python libraries. 616 if !isPythonLibModule(child) { 617 panic(fmt.Errorf( 618 "the dependency %q of module %q is not Python library!", 619 ctx.ModuleName(), ctx.OtherModuleName(child))) 620 } 621 if dep, ok := child.(PythonDependency); ok { 622 srcs := dep.GetSrcsPathMappings() 623 for _, path := range srcs { 624 if !fillInMap(ctx, destToPySrcs, 625 path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child)) { 626 continue 627 } 628 } 629 data := dep.GetDataPathMappings() 630 for _, path := range data { 631 fillInMap(ctx, destToPyData, 632 path.dest, path.src.String(), ctx.ModuleName(), ctx.OtherModuleName(child)) 633 } 634 p.depsSrcsZips = append(p.depsSrcsZips, dep.GetSrcsZip()) 635 } 636 return true 637 }) 638} 639 640func fillInMap(ctx android.ModuleContext, m map[string]string, 641 key, value, curModule, otherModule string) bool { 642 if oldValue, found := m[key]; found { 643 ctx.ModuleErrorf("found two files to be placed at the same location within zip %q."+ 644 " First file: in module %s at path %q."+ 645 " Second file: in module %s at path %q.", 646 key, curModule, oldValue, otherModule, value) 647 return false 648 } else { 649 m[key] = value 650 } 651 652 return true 653} 654 655func (p *Module) InstallInData() bool { 656 return true 657} 658 659var Bool = proptools.Bool 660var BoolDefault = proptools.BoolDefault 661var String = proptools.String 662