|  | // 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}) | 
|  | } |