github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/python/python.go (about) 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 15 package python 16 17 // This file contains the "Base" module type for building Python program. 18 19 import ( 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 32 func 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. 39 type 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:"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:"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. 61 type 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 of current module are copied to 67 // "runfiles/" tree directory directly. 68 Pkg_path *string `android:"arch_variant"` 69 70 // true, if the Python module is used internally, eg, Python std libs. 71 Is_internal *bool `android:"arch_variant"` 72 73 // list of source (.py) files compatible both with Python2 and Python3 used to compile the 74 // Python module. 75 // srcs may reference the outputs of other modules that produce source files like genrule 76 // or filegroup using the syntax ":module". 77 // Srcs has to be non-empty. 78 Srcs []string `android:"arch_variant"` 79 80 // list of source files that should not be used to build the C/C++ module. 81 // This is most useful in the arch/multilib variants to remove non-common files 82 Exclude_srcs []string `android:"arch_variant"` 83 84 // list of files or filegroup modules that provide data that should be installed alongside 85 // the test. the file extension can be arbitrary except for (.py). 86 Data []string `android:"arch_variant"` 87 88 // list of the Python libraries compatible both with Python2 and Python3. 89 Libs []string `android:"arch_variant"` 90 91 Version struct { 92 // all the "srcs" or Python dependencies that are to be used only for Python2. 93 Py2 VersionProperties `android:"arch_variant"` 94 95 // all the "srcs" or Python dependencies that are to be used only for Python3. 96 Py3 VersionProperties `android:"arch_variant"` 97 } `android:"arch_variant"` 98 99 // the actual version each module uses after variations created. 100 // this property name is hidden from users' perspectives, and soong will populate it during 101 // runtime. 102 Actual_version string `blueprint:"mutated"` 103 } 104 105 type pathMapping struct { 106 dest string 107 src android.Path 108 } 109 110 type Module struct { 111 android.ModuleBase 112 android.DefaultableModuleBase 113 114 properties BaseProperties 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 // soong_zip arguments of all its dependencies. 136 depsParSpecs []parSpec 137 138 // Python runfiles paths of all its dependencies. 139 depsPyRunfiles []string 140 141 // (.intermediate) module output path as installation source. 142 installSource android.OptionalPath 143 144 // the soong_zip arguments for zipping current module source/data files. 145 parSpec parSpec 146 147 subAndroidMkOnce map[subAndroidMkProvider]bool 148 } 149 150 func newModule(hod android.HostOrDeviceSupported, multilib android.Multilib) *Module { 151 return &Module{ 152 hod: hod, 153 multilib: multilib, 154 } 155 } 156 157 type bootstrapper interface { 158 bootstrapperProps() []interface{} 159 bootstrap(ctx android.ModuleContext, Actual_version string, embedded_launcher bool, 160 srcsPathMappings []pathMapping, parSpec parSpec, 161 depsPyRunfiles []string, depsParSpecs []parSpec) android.OptionalPath 162 } 163 164 type installer interface { 165 install(ctx android.ModuleContext, path android.Path) 166 } 167 168 type PythonDependency interface { 169 GetSrcsPathMappings() []pathMapping 170 GetDataPathMappings() []pathMapping 171 GetParSpec() parSpec 172 } 173 174 func (p *Module) GetSrcsPathMappings() []pathMapping { 175 return p.srcsPathMappings 176 } 177 178 func (p *Module) GetDataPathMappings() []pathMapping { 179 return p.dataPathMappings 180 } 181 182 func (p *Module) GetParSpec() parSpec { 183 return p.parSpec 184 } 185 186 var _ PythonDependency = (*Module)(nil) 187 188 var _ android.AndroidMkDataProvider = (*Module)(nil) 189 190 func (p *Module) Init() android.Module { 191 192 p.AddProperties(&p.properties) 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 203 type dependencyTag struct { 204 blueprint.BaseDependencyTag 205 name string 206 } 207 208 var ( 209 pythonLibTag = dependencyTag{name: "pythonLib"} 210 launcherTag = dependencyTag{name: "launcher"} 211 pyIdentifierRegexp = regexp.MustCompile(`^([a-z]|[A-Z]|_)([a-z]|[A-Z]|[0-9]|_)*$`) 212 pyExt = ".py" 213 pyVersion2 = "PY2" 214 pyVersion3 = "PY3" 215 initFileName = "__init__.py" 216 mainFileName = "__main__.py" 217 entryPointFile = "entry_point.txt" 218 parFileExt = ".zip" 219 runFiles = "runfiles" 220 internal = "internal" 221 ) 222 223 // create version variants for modules. 224 func versionSplitMutator() func(android.BottomUpMutatorContext) { 225 return func(mctx android.BottomUpMutatorContext) { 226 if base, ok := mctx.Module().(*Module); ok { 227 versionNames := []string{} 228 if base.properties.Version.Py2.Enabled != nil && 229 *(base.properties.Version.Py2.Enabled) == true { 230 versionNames = append(versionNames, pyVersion2) 231 } 232 if !(base.properties.Version.Py3.Enabled != nil && 233 *(base.properties.Version.Py3.Enabled) == false) { 234 versionNames = append(versionNames, pyVersion3) 235 } 236 modules := mctx.CreateVariations(versionNames...) 237 for i, v := range versionNames { 238 // set the actual version for Python module. 239 modules[i].(*Module).properties.Actual_version = v 240 } 241 } 242 } 243 } 244 245 func (p *Module) HostToolPath() android.OptionalPath { 246 if p.installer == nil { 247 // python_library is just meta module, and doesn't have any installer. 248 return android.OptionalPath{} 249 } 250 return android.OptionalPathForPath(p.installer.(*binaryDecorator).path) 251 } 252 253 func (p *Module) isEmbeddedLauncherEnabled(actual_version string) bool { 254 switch actual_version { 255 case pyVersion2: 256 return proptools.Bool(p.properties.Version.Py2.Embedded_launcher) 257 case pyVersion3: 258 return proptools.Bool(p.properties.Version.Py3.Embedded_launcher) 259 } 260 261 return false 262 } 263 264 func (p *Module) DepsMutator(ctx android.BottomUpMutatorContext) { 265 // deps from "data". 266 android.ExtractSourcesDeps(ctx, p.properties.Data) 267 // deps from "srcs". 268 android.ExtractSourcesDeps(ctx, p.properties.Srcs) 269 android.ExtractSourcesDeps(ctx, p.properties.Exclude_srcs) 270 271 switch p.properties.Actual_version { 272 case pyVersion2: 273 // deps from "version.py2.srcs" property. 274 android.ExtractSourcesDeps(ctx, p.properties.Version.Py2.Srcs) 275 android.ExtractSourcesDeps(ctx, p.properties.Version.Py2.Exclude_srcs) 276 277 ctx.AddVariationDependencies(nil, pythonLibTag, 278 uniqueLibs(ctx, p.properties.Libs, "version.py2.libs", 279 p.properties.Version.Py2.Libs)...) 280 281 if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled(pyVersion2) { 282 ctx.AddVariationDependencies(nil, pythonLibTag, "py2-stdlib") 283 ctx.AddFarVariationDependencies([]blueprint.Variation{ 284 {"arch", ctx.Target().String()}, 285 }, launcherTag, "py2-launcher") 286 } 287 288 case pyVersion3: 289 // deps from "version.py3.srcs" property. 290 android.ExtractSourcesDeps(ctx, p.properties.Version.Py3.Srcs) 291 android.ExtractSourcesDeps(ctx, p.properties.Version.Py3.Exclude_srcs) 292 293 ctx.AddVariationDependencies(nil, pythonLibTag, 294 uniqueLibs(ctx, p.properties.Libs, "version.py3.libs", 295 p.properties.Version.Py3.Libs)...) 296 297 if p.bootstrapper != nil && p.isEmbeddedLauncherEnabled(pyVersion3) { 298 //TODO(nanzhang): Add embedded launcher for Python3. 299 ctx.PropertyErrorf("version.py3.embedded_launcher", 300 "is not supported yet for Python3.") 301 } 302 default: 303 panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.", 304 p.properties.Actual_version, ctx.ModuleName())) 305 } 306 } 307 308 // check "libs" duplicates from current module dependencies. 309 func uniqueLibs(ctx android.BottomUpMutatorContext, 310 commonLibs []string, versionProp string, versionLibs []string) []string { 311 set := make(map[string]string) 312 ret := []string{} 313 314 // deps from "libs" property. 315 for _, l := range commonLibs { 316 if _, found := set[l]; found { 317 ctx.PropertyErrorf("libs", "%q has duplicates within libs.", l) 318 } else { 319 set[l] = "libs" 320 ret = append(ret, l) 321 } 322 } 323 // deps from "version.pyX.libs" property. 324 for _, l := range versionLibs { 325 if _, found := set[l]; found { 326 ctx.PropertyErrorf(versionProp, "%q has duplicates within %q.", set[l]) 327 } else { 328 set[l] = versionProp 329 ret = append(ret, l) 330 } 331 } 332 333 return ret 334 } 335 336 func (p *Module) GenerateAndroidBuildActions(ctx android.ModuleContext) { 337 p.GeneratePythonBuildActions(ctx) 338 339 if p.bootstrapper != nil { 340 // TODO(nanzhang): Since embedded launcher is not supported for Python3 for now, 341 // so we initialize "embedded_launcher" to false. 342 embedded_launcher := false 343 if p.properties.Actual_version == pyVersion2 { 344 embedded_launcher = p.isEmbeddedLauncherEnabled(pyVersion2) 345 } 346 p.installSource = p.bootstrapper.bootstrap(ctx, p.properties.Actual_version, 347 embedded_launcher, p.srcsPathMappings, p.parSpec, p.depsPyRunfiles, 348 p.depsParSpecs) 349 } 350 351 if p.installer != nil && p.installSource.Valid() { 352 p.installer.install(ctx, p.installSource.Path()) 353 } 354 355 } 356 357 func (p *Module) GeneratePythonBuildActions(ctx android.ModuleContext) { 358 // expand python files from "srcs" property. 359 srcs := p.properties.Srcs 360 exclude_srcs := p.properties.Exclude_srcs 361 switch p.properties.Actual_version { 362 case pyVersion2: 363 srcs = append(srcs, p.properties.Version.Py2.Srcs...) 364 exclude_srcs = append(exclude_srcs, p.properties.Version.Py2.Exclude_srcs...) 365 case pyVersion3: 366 srcs = append(srcs, p.properties.Version.Py3.Srcs...) 367 exclude_srcs = append(exclude_srcs, p.properties.Version.Py3.Exclude_srcs...) 368 default: 369 panic(fmt.Errorf("unknown Python Actual_version: %q for module: %q.", 370 p.properties.Actual_version, ctx.ModuleName())) 371 } 372 expandedSrcs := ctx.ExpandSources(srcs, exclude_srcs) 373 if len(expandedSrcs) == 0 { 374 ctx.ModuleErrorf("doesn't have any source files!") 375 } 376 377 // expand data files from "data" property. 378 expandedData := ctx.ExpandSources(p.properties.Data, nil) 379 380 // sanitize pkg_path. 381 pkg_path := String(p.properties.Pkg_path) 382 if pkg_path != "" { 383 pkg_path = filepath.Clean(String(p.properties.Pkg_path)) 384 if pkg_path == ".." || strings.HasPrefix(pkg_path, "../") || 385 strings.HasPrefix(pkg_path, "/") { 386 ctx.PropertyErrorf("pkg_path", 387 "%q must be a relative path contained in par file.", 388 String(p.properties.Pkg_path)) 389 return 390 } 391 if p.properties.Is_internal != nil && *p.properties.Is_internal { 392 // pkg_path starts from "internal/" implicitly. 393 pkg_path = filepath.Join(internal, pkg_path) 394 } else { 395 // pkg_path starts from "runfiles/" implicitly. 396 pkg_path = filepath.Join(runFiles, pkg_path) 397 } 398 } else { 399 if p.properties.Is_internal != nil && *p.properties.Is_internal { 400 // pkg_path starts from "runfiles/" implicitly. 401 pkg_path = internal 402 } else { 403 // pkg_path starts from "runfiles/" implicitly. 404 pkg_path = runFiles 405 } 406 } 407 408 p.genModulePathMappings(ctx, pkg_path, expandedSrcs, expandedData) 409 410 p.parSpec = p.dumpFileList(ctx, pkg_path) 411 412 p.uniqWholeRunfilesTree(ctx) 413 } 414 415 // generate current module unique pathMappings: <dest: runfiles_path, src: source_path> 416 // for python/data files. 417 func (p *Module) genModulePathMappings(ctx android.ModuleContext, pkg_path string, 418 expandedSrcs, expandedData android.Paths) { 419 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to 420 // check duplicates. 421 destToPySrcs := make(map[string]string) 422 destToPyData := make(map[string]string) 423 424 for _, s := range expandedSrcs { 425 if s.Ext() != pyExt { 426 ctx.PropertyErrorf("srcs", "found non (.py) file: %q!", s.String()) 427 continue 428 } 429 runfilesPath := filepath.Join(pkg_path, s.Rel()) 430 identifiers := strings.Split(strings.TrimSuffix(runfilesPath, pyExt), "/") 431 for _, token := range identifiers { 432 if !pyIdentifierRegexp.MatchString(token) { 433 ctx.PropertyErrorf("srcs", "the path %q contains invalid token %q.", 434 runfilesPath, token) 435 } 436 } 437 if fillInMap(ctx, destToPySrcs, runfilesPath, s.String(), p.Name(), p.Name()) { 438 p.srcsPathMappings = append(p.srcsPathMappings, 439 pathMapping{dest: runfilesPath, src: s}) 440 } 441 } 442 443 for _, d := range expandedData { 444 if d.Ext() == pyExt { 445 ctx.PropertyErrorf("data", "found (.py) file: %q!", d.String()) 446 continue 447 } 448 runfilesPath := filepath.Join(pkg_path, d.Rel()) 449 if fillInMap(ctx, destToPyData, runfilesPath, d.String(), p.Name(), p.Name()) { 450 p.dataPathMappings = append(p.dataPathMappings, 451 pathMapping{dest: runfilesPath, src: d}) 452 } 453 } 454 455 } 456 457 // register build actions to dump filelist to disk. 458 func (p *Module) dumpFileList(ctx android.ModuleContext, pkg_path string) parSpec { 459 relativeRootMap := make(map[string]android.Paths) 460 // the soong_zip params in order to pack current module's Python/data files. 461 ret := parSpec{rootPrefix: pkg_path} 462 463 pathMappings := append(p.srcsPathMappings, p.dataPathMappings...) 464 465 // "srcs" or "data" properties may have filegroup so it might happen that 466 // the relative root for each source path is different. 467 for _, path := range pathMappings { 468 var relativeRoot string 469 relativeRoot = strings.TrimSuffix(path.src.String(), path.src.Rel()) 470 if v, found := relativeRootMap[relativeRoot]; found { 471 relativeRootMap[relativeRoot] = append(v, path.src) 472 } else { 473 relativeRootMap[relativeRoot] = android.Paths{path.src} 474 } 475 } 476 477 var keys []string 478 479 // in order to keep stable order of soong_zip params, we sort the keys here. 480 for k := range relativeRootMap { 481 keys = append(keys, k) 482 } 483 sort.Strings(keys) 484 485 for _, k := range keys { 486 // use relative root as filelist name. 487 fileListPath := registerBuildActionForModuleFileList( 488 ctx, strings.Replace(k, "/", "_", -1), relativeRootMap[k]) 489 ret.fileListSpecs = append(ret.fileListSpecs, 490 fileListSpec{fileList: fileListPath, relativeRoot: k}) 491 } 492 493 return ret 494 } 495 496 func isPythonLibModule(module blueprint.Module) bool { 497 if m, ok := module.(*Module); ok { 498 // Python library has no bootstrapper or installer. 499 if m.bootstrapper != nil || m.installer != nil { 500 return false 501 } 502 return true 503 } 504 return false 505 } 506 507 // check Python source/data files duplicates from current module and its whole dependencies. 508 func (p *Module) uniqWholeRunfilesTree(ctx android.ModuleContext) { 509 // fetch <runfiles_path, source_path> pairs from "src" and "data" properties to 510 // check duplicates. 511 destToPySrcs := make(map[string]string) 512 destToPyData := make(map[string]string) 513 514 for _, path := range p.srcsPathMappings { 515 destToPySrcs[path.dest] = path.src.String() 516 } 517 for _, path := range p.dataPathMappings { 518 destToPyData[path.dest] = path.src.String() 519 } 520 521 // visit all its dependencies in depth first. 522 ctx.VisitDepsDepthFirst(func(module android.Module) { 523 if ctx.OtherModuleDependencyTag(module) != pythonLibTag { 524 return 525 } 526 // Python module cannot depend on modules, except for Python library. 527 if !isPythonLibModule(module) { 528 panic(fmt.Errorf( 529 "the dependency %q of module %q is not Python library!", 530 ctx.ModuleName(), ctx.OtherModuleName(module))) 531 } 532 if dep, ok := module.(PythonDependency); ok { 533 srcs := dep.GetSrcsPathMappings() 534 for _, path := range srcs { 535 if !fillInMap(ctx, destToPySrcs, 536 path.dest, path.src.String(), ctx.ModuleName(), 537 ctx.OtherModuleName(module)) { 538 continue 539 } 540 // binary needs the Python runfiles paths from all its 541 // dependencies to fill __init__.py in each runfiles dir. 542 p.depsPyRunfiles = append(p.depsPyRunfiles, path.dest) 543 } 544 data := dep.GetDataPathMappings() 545 for _, path := range data { 546 fillInMap(ctx, destToPyData, 547 path.dest, path.src.String(), ctx.ModuleName(), 548 ctx.OtherModuleName(module)) 549 } 550 // binary needs the soong_zip arguments from all its 551 // dependencies to generate executable par file. 552 p.depsParSpecs = append(p.depsParSpecs, dep.GetParSpec()) 553 } 554 }) 555 } 556 557 func fillInMap(ctx android.ModuleContext, m map[string]string, 558 key, value, curModule, otherModule string) bool { 559 if oldValue, found := m[key]; found { 560 ctx.ModuleErrorf("found two files to be placed at the same runfiles location %q."+ 561 " First file: in module %s at path %q."+ 562 " Second file: in module %s at path %q.", 563 key, curModule, oldValue, otherModule, value) 564 return false 565 } else { 566 m[key] = value 567 } 568 569 return true 570 } 571 572 func (p *Module) InstallInData() bool { 573 return true 574 } 575 576 var Bool = proptools.Bool 577 var String = proptools.String