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