| // Copyright 2016 Google Inc. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package android |
| |
| import ( |
| "bytes" |
| "fmt" |
| "path/filepath" |
| "runtime" |
| "sort" |
| "strings" |
| |
| "github.com/google/blueprint" |
| "github.com/google/blueprint/pathtools" |
| "github.com/google/blueprint/proptools" |
| ) |
| |
| func init() { |
| RegisterMakeVarsProvider(pctx, androidMakeVarsProvider) |
| } |
| |
| func androidMakeVarsProvider(ctx MakeVarsContext) { |
| ctx.Strict("MIN_SUPPORTED_SDK_VERSION", ctx.Config().MinSupportedSdkVersion().String()) |
| } |
| |
| // ///////////////////////////////////////////////////////////////////////////// |
| |
| // BaseMakeVarsContext contains the common functions for other packages to use |
| // to declare make variables |
| type BaseMakeVarsContext interface { |
| Config() Config |
| DeviceConfig() DeviceConfig |
| AddNinjaFileDeps(deps ...string) |
| |
| Failed() bool |
| |
| // These are equivalent to Strict and Check, but do not attempt to |
| // evaluate the values before writing them to the Makefile. They can |
| // be used when all ninja variables have already been evaluated through |
| // Eval(). |
| StrictRaw(name, value string) |
| CheckRaw(name, value string) |
| |
| // GlobWithDeps returns a list of files that match the specified pattern but do not match any |
| // of the patterns in excludes. It also adds efficient dependencies to rerun the primary |
| // builder whenever a file matching the pattern as added or removed, without rerunning if a |
| // file that does not match the pattern is added to a searched directory. |
| GlobWithDeps(pattern string, excludes []string) ([]string, error) |
| |
| // Phony creates a phony rule in Make, which will allow additional DistForGoal |
| // dependencies to be added to it. Phony can be called on the same name multiple |
| // times to add additional dependencies. |
| Phony(names string, deps ...Path) |
| |
| // DistForGoal creates a rule to copy one or more Paths to the artifacts |
| // directory on the build server when the specified goal is built. |
| DistForGoal(goal string, paths ...Path) |
| |
| // DistForGoalWithFilename creates a rule to copy a Path to the artifacts |
| // directory on the build server with the given filename when the specified |
| // goal is built. |
| DistForGoalWithFilename(goal string, path Path, filename string) |
| |
| // DistForGoals creates a rule to copy one or more Paths to the artifacts |
| // directory on the build server when any of the specified goals are built. |
| DistForGoals(goals []string, paths ...Path) |
| |
| // DistForGoalsWithFilename creates a rule to copy a Path to the artifacts |
| // directory on the build server with the given filename when any of the |
| // specified goals are built. |
| DistForGoalsWithFilename(goals []string, path Path, filename string) |
| } |
| |
| // MakeVarsContext contains the set of functions available for MakeVarsProvider |
| // and SingletonMakeVarsProvider implementations. |
| type MakeVarsContext interface { |
| BaseMakeVarsContext |
| |
| ModuleName(module blueprint.Module) string |
| ModuleDir(module blueprint.Module) string |
| ModuleSubDir(module blueprint.Module) string |
| ModuleType(module blueprint.Module) string |
| ModuleProvider(module blueprint.Module, key blueprint.ProviderKey) interface{} |
| BlueprintFile(module blueprint.Module) string |
| |
| ModuleErrorf(module blueprint.Module, format string, args ...interface{}) |
| Errorf(format string, args ...interface{}) |
| |
| VisitAllModules(visit func(Module)) |
| VisitAllModulesIf(pred func(Module) bool, visit func(Module)) |
| |
| // Verify the make variable matches the Soong version, fail the build |
| // if it does not. If the make variable is empty, just set it. |
| Strict(name, ninjaStr string) |
| // Check to see if the make variable matches the Soong version, warn if |
| // it does not. If the make variable is empty, just set it. |
| Check(name, ninjaStr string) |
| |
| // These are equivalent to the above, but sort the make and soong |
| // variables before comparing them. They also show the unique entries |
| // in each list when displaying the difference, instead of the entire |
| // string. |
| StrictSorted(name, ninjaStr string) |
| CheckSorted(name, ninjaStr string) |
| |
| // Evaluates a ninja string and returns the result. Used if more |
| // complicated modification needs to happen before giving it to Make. |
| Eval(ninjaStr string) (string, error) |
| } |
| |
| // MakeVarsModuleContext contains the set of functions available for modules |
| // implementing the ModuleMakeVarsProvider interface. |
| type MakeVarsModuleContext interface { |
| BaseMakeVarsContext |
| } |
| |
| var _ PathContext = MakeVarsContext(nil) |
| |
| type MakeVarsProvider func(ctx MakeVarsContext) |
| |
| func RegisterMakeVarsProvider(pctx PackageContext, provider MakeVarsProvider) { |
| makeVarsInitProviders = append(makeVarsInitProviders, makeVarsProvider{pctx, provider}) |
| } |
| |
| // SingletonMakeVarsProvider is a Singleton with an extra method to provide extra values to be exported to Make. |
| type SingletonMakeVarsProvider interface { |
| // MakeVars uses a MakeVarsContext to provide extra values to be exported to Make. |
| MakeVars(ctx MakeVarsContext) |
| } |
| |
| var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey") |
| |
| func getSingletonMakevarsProviders(config Config) *[]makeVarsProvider { |
| return config.Once(singletonMakeVarsProvidersKey, func() interface{} { |
| return &[]makeVarsProvider{} |
| }).(*[]makeVarsProvider) |
| } |
| |
| // registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to |
| // the list of MakeVarsProviders to run. |
| func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) { |
| // Singletons are registered on the Context and may be different between different Contexts, |
| // for example when running multiple tests. Store the SingletonMakeVarsProviders in the |
| // Config so they are attached to the Context. |
| singletonMakeVarsProviders := getSingletonMakevarsProviders(config) |
| |
| *singletonMakeVarsProviders = append(*singletonMakeVarsProviders, |
| makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)}) |
| } |
| |
| // singletonMakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider. |
| func singletonMakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider { |
| return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) } |
| } |
| |
| // ModuleMakeVarsProvider is a Module with an extra method to provide extra values to be exported to Make. |
| type ModuleMakeVarsProvider interface { |
| Module |
| |
| // MakeVars uses a MakeVarsModuleContext to provide extra values to be exported to Make. |
| MakeVars(ctx MakeVarsModuleContext) |
| } |
| |
| // ///////////////////////////////////////////////////////////////////////////// |
| |
| func makeVarsSingletonFunc() Singleton { |
| return &makeVarsSingleton{} |
| } |
| |
| type makeVarsSingleton struct { |
| varsForTesting []makeVarsVariable |
| installsForTesting []byte |
| } |
| |
| type makeVarsProvider struct { |
| pctx PackageContext |
| call MakeVarsProvider |
| } |
| |
| // Collection of makevars providers that are registered in init() methods. |
| var makeVarsInitProviders []makeVarsProvider |
| |
| type makeVarsContext struct { |
| SingletonContext |
| config Config |
| pctx PackageContext |
| vars []makeVarsVariable |
| phonies []phony |
| dists []dist |
| } |
| |
| var _ MakeVarsContext = &makeVarsContext{} |
| |
| type makeVarsVariable struct { |
| name string |
| value string |
| sort bool |
| strict bool |
| } |
| |
| type phony struct { |
| name string |
| deps []string |
| } |
| |
| type dist struct { |
| goals []string |
| paths []string |
| } |
| |
| func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) { |
| if !ctx.Config().KatiEnabled() { |
| return |
| } |
| |
| outFile := absolutePath(PathForOutput(ctx, |
| "make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String()) |
| |
| lateOutFile := absolutePath(PathForOutput(ctx, |
| "late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String()) |
| |
| installsFile := absolutePath(PathForOutput(ctx, |
| "installs"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String()) |
| |
| if ctx.Failed() { |
| return |
| } |
| |
| var vars []makeVarsVariable |
| var dists []dist |
| var phonies []phony |
| var katiInstalls []katiInstall |
| var katiSymlinks []katiInstall |
| |
| providers := append([]makeVarsProvider(nil), makeVarsInitProviders...) |
| providers = append(providers, *getSingletonMakevarsProviders(ctx.Config())...) |
| |
| for _, provider := range providers { |
| mctx := &makeVarsContext{ |
| SingletonContext: ctx, |
| pctx: provider.pctx, |
| } |
| |
| provider.call(mctx) |
| |
| vars = append(vars, mctx.vars...) |
| phonies = append(phonies, mctx.phonies...) |
| dists = append(dists, mctx.dists...) |
| } |
| |
| ctx.VisitAllModules(func(m Module) { |
| if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled() { |
| mctx := &makeVarsContext{ |
| SingletonContext: ctx, |
| } |
| |
| provider.MakeVars(mctx) |
| |
| vars = append(vars, mctx.vars...) |
| phonies = append(phonies, mctx.phonies...) |
| dists = append(dists, mctx.dists...) |
| } |
| |
| if m.ExportedToMake() { |
| katiInstalls = append(katiInstalls, m.base().katiInstalls...) |
| katiSymlinks = append(katiSymlinks, m.base().katiSymlinks...) |
| } |
| }) |
| |
| if ctx.Failed() { |
| return |
| } |
| |
| sort.Slice(vars, func(i, j int) bool { |
| return vars[i].name < vars[j].name |
| }) |
| sort.Slice(phonies, func(i, j int) bool { |
| return phonies[i].name < phonies[j].name |
| }) |
| lessArr := func(a, b []string) bool { |
| if len(a) == len(b) { |
| for i := range a { |
| if a[i] < b[i] { |
| return true |
| } |
| } |
| return false |
| } |
| return len(a) < len(b) |
| } |
| sort.Slice(dists, func(i, j int) bool { |
| return lessArr(dists[i].goals, dists[j].goals) || lessArr(dists[i].paths, dists[j].paths) |
| }) |
| |
| outBytes := s.writeVars(vars) |
| |
| if err := pathtools.WriteFileIfChanged(outFile, outBytes, 0666); err != nil { |
| ctx.Errorf(err.Error()) |
| } |
| |
| lateOutBytes := s.writeLate(phonies, dists) |
| |
| if err := pathtools.WriteFileIfChanged(lateOutFile, lateOutBytes, 0666); err != nil { |
| ctx.Errorf(err.Error()) |
| } |
| |
| installsBytes := s.writeInstalls(katiInstalls, katiSymlinks) |
| if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil { |
| ctx.Errorf(err.Error()) |
| } |
| |
| // Only save state for tests when testing. |
| if ctx.Config().RunningInsideUnitTest() { |
| s.varsForTesting = vars |
| s.installsForTesting = installsBytes |
| } |
| } |
| |
| func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte { |
| buf := &bytes.Buffer{} |
| |
| fmt.Fprint(buf, `# Autogenerated file |
| |
| # Compares SOONG_$(1) against $(1), and warns if they are not equal. |
| # |
| # If the original variable is empty, then just set it to the SOONG_ version. |
| # |
| # $(1): Name of the variable to check |
| # $(2): If not-empty, sort the values before comparing |
| # $(3): Extra snippet to run if it does not match |
| define soong-compare-var |
| ifneq ($$($(1)),) |
| my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1)))) |
| my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1))) |
| ifneq ($$(my_val_make),$$(my_val_soong)) |
| $$(warning $(1) does not match between Make and Soong:) |
| $(if $(2),$$(warning Make adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make))) |
| $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong))) |
| $(3) |
| endif |
| my_val_make := |
| my_val_soong := |
| else |
| $(1) := $$(SOONG_$(1)) |
| endif |
| .KATI_READONLY := $(1) SOONG_$(1) |
| endef |
| |
| my_check_failed := false |
| |
| `) |
| |
| // Write all the strict checks out first so that if one of them errors, |
| // we get all of the strict errors printed, but not the non-strict |
| // warnings. |
| for _, v := range vars { |
| if !v.strict { |
| continue |
| } |
| |
| sort := "" |
| if v.sort { |
| sort = "true" |
| } |
| |
| fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) |
| fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort) |
| } |
| |
| fmt.Fprint(buf, ` |
| ifneq ($(my_check_failed),false) |
| $(error Soong variable check failed) |
| endif |
| my_check_failed := |
| |
| |
| `) |
| |
| for _, v := range vars { |
| if v.strict { |
| continue |
| } |
| |
| sort := "" |
| if v.sort { |
| sort = "true" |
| } |
| |
| fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) |
| fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort) |
| } |
| |
| fmt.Fprintln(buf, "\nsoong-compare-var :=") |
| |
| fmt.Fprintln(buf) |
| |
| return buf.Bytes() |
| } |
| |
| func (s *makeVarsSingleton) writeLate(phonies []phony, dists []dist) []byte { |
| buf := &bytes.Buffer{} |
| |
| fmt.Fprint(buf, `# Autogenerated file |
| |
| # Values written by Soong read after parsing all Android.mk files. |
| |
| |
| `) |
| |
| for _, phony := range phonies { |
| fmt.Fprintf(buf, ".PHONY: %s\n", phony.name) |
| fmt.Fprintf(buf, "%s: %s\n", phony.name, strings.Join(phony.deps, "\\\n ")) |
| } |
| |
| fmt.Fprintln(buf) |
| |
| for _, dist := range dists { |
| fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " ")) |
| fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n", |
| strings.Join(dist.goals, " "), strings.Join(dist.paths, " ")) |
| } |
| |
| return buf.Bytes() |
| } |
| |
| // writeInstalls writes the list of install rules generated by Soong to a makefile. The rules |
| // are exported to Make instead of written directly to the ninja file so that main.mk can add |
| // the dependencies from the `required` property that are hard to resolve in Soong. |
| func (s *makeVarsSingleton) writeInstalls(installs, symlinks []katiInstall) []byte { |
| buf := &bytes.Buffer{} |
| |
| fmt.Fprint(buf, `# Autogenerated file |
| |
| # Values written by Soong to generate install rules that can be amended by Kati. |
| |
| |
| `) |
| |
| preserveSymlinksFlag := "-d" |
| if runtime.GOOS == "darwin" { |
| preserveSymlinksFlag = "-R" |
| } |
| |
| for _, install := range installs { |
| // Write a rule for each install request in the form: |
| // to: from [ deps ] [ | order only deps ] |
| // cp -f -d $< $@ [ && chmod +x $@ ] |
| fmt.Fprintf(buf, "%s: %s", install.to.String(), install.from.String()) |
| for _, dep := range install.implicitDeps { |
| fmt.Fprintf(buf, " %s", dep.String()) |
| } |
| if extraFiles := install.extraFiles; extraFiles != nil { |
| fmt.Fprintf(buf, " %s", extraFiles.zip.String()) |
| } |
| if len(install.orderOnlyDeps) > 0 { |
| fmt.Fprintf(buf, " |") |
| } |
| for _, dep := range install.orderOnlyDeps { |
| fmt.Fprintf(buf, " %s", dep.String()) |
| } |
| fmt.Fprintln(buf) |
| fmt.Fprintln(buf, "\t@echo \"Install: $@\"") |
| fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@\n", preserveSymlinksFlag) |
| if install.executable { |
| fmt.Fprintf(buf, "\tchmod +x $@\n") |
| } |
| if extraFiles := install.extraFiles; extraFiles != nil { |
| fmt.Fprintf(buf, "\t( unzip -qDD -d '%s' '%s' 2>&1 | grep -v \"zipfile is empty\"; exit $${PIPESTATUS[0]} ) || \\\n", extraFiles.dir.String(), extraFiles.zip.String()) |
| fmt.Fprintf(buf, "\t ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )\n") |
| } |
| fmt.Fprintln(buf) |
| } |
| |
| for _, symlink := range symlinks { |
| fmt.Fprintf(buf, "%s:", symlink.to.String()) |
| if symlink.from != nil { |
| // The symlink doesn't need updating when the target is modified, but we sometimes |
| // have a dependency on a symlink to a binary instead of to the binary directly, and |
| // the mtime of the symlink must be updated when the binary is modified, so use a |
| // normal dependency here instead of an order-only dependency. |
| fmt.Fprintf(buf, " %s", symlink.from.String()) |
| } |
| for _, dep := range symlink.implicitDeps { |
| fmt.Fprintf(buf, " %s", dep.String()) |
| } |
| if len(symlink.orderOnlyDeps) > 0 { |
| fmt.Fprintf(buf, " |") |
| } |
| for _, dep := range symlink.orderOnlyDeps { |
| fmt.Fprintf(buf, " %s", dep.String()) |
| } |
| fmt.Fprintln(buf) |
| |
| fromStr := "" |
| if symlink.from != nil { |
| rel, err := filepath.Rel(filepath.Dir(symlink.to.String()), symlink.from.String()) |
| if err != nil { |
| panic(fmt.Errorf("failed to find relative path for symlink from %q to %q: %w", |
| symlink.from.String(), symlink.to.String(), err)) |
| } |
| fromStr = rel |
| } else { |
| fromStr = symlink.absFrom |
| } |
| |
| fmt.Fprintln(buf, "\t@echo \"Symlink: $@\"") |
| fmt.Fprintf(buf, "\trm -f $@ && ln -sfn %s $@", fromStr) |
| fmt.Fprintln(buf) |
| fmt.Fprintln(buf) |
| } |
| |
| return buf.Bytes() |
| } |
| |
| func (c *makeVarsContext) DeviceConfig() DeviceConfig { |
| return DeviceConfig{c.Config().deviceConfig} |
| } |
| |
| var ninjaDescaper = strings.NewReplacer("$$", "$") |
| |
| func (c *makeVarsContext) Eval(ninjaStr string) (string, error) { |
| s, err := c.SingletonContext.Eval(c.pctx, ninjaStr) |
| if err != nil { |
| return "", err |
| } |
| // SingletonContext.Eval returns an exapnded string that is valid for a ninja file, de-escape $$ to $ for use |
| // in a Makefile |
| return ninjaDescaper.Replace(s), nil |
| } |
| |
| func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) { |
| c.vars = append(c.vars, makeVarsVariable{ |
| name: name, |
| value: value, |
| strict: strict, |
| sort: sort, |
| }) |
| } |
| |
| func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) { |
| value, err := c.Eval(ninjaStr) |
| if err != nil { |
| c.SingletonContext.Errorf(err.Error()) |
| } |
| c.addVariableRaw(name, value, strict, sort) |
| } |
| |
| func (c *makeVarsContext) addPhony(name string, deps []string) { |
| c.phonies = append(c.phonies, phony{name, deps}) |
| } |
| |
| func (c *makeVarsContext) addDist(goals []string, paths []string) { |
| c.dists = append(c.dists, dist{ |
| goals: goals, |
| paths: paths, |
| }) |
| } |
| |
| func (c *makeVarsContext) Strict(name, ninjaStr string) { |
| c.addVariable(name, ninjaStr, true, false) |
| } |
| func (c *makeVarsContext) StrictSorted(name, ninjaStr string) { |
| c.addVariable(name, ninjaStr, true, true) |
| } |
| func (c *makeVarsContext) StrictRaw(name, value string) { |
| c.addVariableRaw(name, value, true, false) |
| } |
| |
| func (c *makeVarsContext) Check(name, ninjaStr string) { |
| c.addVariable(name, ninjaStr, false, false) |
| } |
| func (c *makeVarsContext) CheckSorted(name, ninjaStr string) { |
| c.addVariable(name, ninjaStr, false, true) |
| } |
| func (c *makeVarsContext) CheckRaw(name, value string) { |
| c.addVariableRaw(name, value, false, false) |
| } |
| |
| func (c *makeVarsContext) Phony(name string, deps ...Path) { |
| c.addPhony(name, Paths(deps).Strings()) |
| } |
| |
| func (c *makeVarsContext) DistForGoal(goal string, paths ...Path) { |
| c.DistForGoals([]string{goal}, paths...) |
| } |
| |
| func (c *makeVarsContext) DistForGoalWithFilename(goal string, path Path, filename string) { |
| c.DistForGoalsWithFilename([]string{goal}, path, filename) |
| } |
| |
| func (c *makeVarsContext) DistForGoals(goals []string, paths ...Path) { |
| c.addDist(goals, Paths(paths).Strings()) |
| } |
| |
| func (c *makeVarsContext) DistForGoalsWithFilename(goals []string, path Path, filename string) { |
| c.addDist(goals, []string{path.String() + ":" + filename}) |
| } |