1// Copyright 2016 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 android 16 17import ( 18 "bytes" 19 "cmp" 20 "fmt" 21 "path/filepath" 22 "runtime" 23 "slices" 24 "sort" 25 "strings" 26 27 "github.com/google/blueprint" 28 "github.com/google/blueprint/pathtools" 29 "github.com/google/blueprint/proptools" 30) 31 32func init() { 33 RegisterMakeVarsProvider(pctx, androidMakeVarsProvider) 34} 35 36func androidMakeVarsProvider(ctx MakeVarsContext) { 37 ctx.Strict("MIN_SUPPORTED_SDK_VERSION", ctx.Config().MinSupportedSdkVersion().String()) 38} 39 40// ///////////////////////////////////////////////////////////////////////////// 41 42// BaseMakeVarsContext contains the common functions for other packages to use 43// to declare make variables 44type BaseMakeVarsContext interface { 45 Config() Config 46 DeviceConfig() DeviceConfig 47 AddNinjaFileDeps(deps ...string) 48 49 Failed() bool 50 51 // These are equivalent to Strict and Check, but do not attempt to 52 // evaluate the values before writing them to the Makefile. They can 53 // be used when all ninja variables have already been evaluated through 54 // Eval(). 55 StrictRaw(name, value string) 56 CheckRaw(name, value string) 57 58 // GlobWithDeps returns a list of files that match the specified pattern but do not match any 59 // of the patterns in excludes. It also adds efficient dependencies to rerun the primary 60 // builder whenever a file matching the pattern as added or removed, without rerunning if a 61 // file that does not match the pattern is added to a searched directory. 62 GlobWithDeps(pattern string, excludes []string) ([]string, error) 63 64 // Phony creates a phony rule in Make, which will allow additional DistForGoal 65 // dependencies to be added to it. Phony can be called on the same name multiple 66 // times to add additional dependencies. 67 Phony(names string, deps ...Path) 68 69 // DistForGoal creates a rule to copy one or more Paths to the artifacts 70 // directory on the build server when the specified goal is built. 71 DistForGoal(goal string, paths ...Path) 72 73 // DistForGoalWithFilename creates a rule to copy a Path to the artifacts 74 // directory on the build server with the given filename when the specified 75 // goal is built. 76 DistForGoalWithFilename(goal string, path Path, filename string) 77 78 // DistForGoals creates a rule to copy one or more Paths to the artifacts 79 // directory on the build server when any of the specified goals are built. 80 DistForGoals(goals []string, paths ...Path) 81 82 // DistForGoalsWithFilename creates a rule to copy a Path to the artifacts 83 // directory on the build server with the given filename when any of the 84 // specified goals are built. 85 DistForGoalsWithFilename(goals []string, path Path, filename string) 86} 87 88// MakeVarsContext contains the set of functions available for MakeVarsProvider 89// and SingletonMakeVarsProvider implementations. 90type MakeVarsContext interface { 91 BaseMakeVarsContext 92 93 ModuleName(module blueprint.Module) string 94 ModuleDir(module blueprint.Module) string 95 ModuleSubDir(module blueprint.Module) string 96 ModuleType(module blueprint.Module) string 97 otherModuleProvider(module blueprint.Module, key blueprint.AnyProviderKey) (any, bool) 98 BlueprintFile(module blueprint.Module) string 99 100 ModuleErrorf(module blueprint.Module, format string, args ...interface{}) 101 OtherModulePropertyErrorf(module Module, property, format string, args ...interface{}) 102 Errorf(format string, args ...interface{}) 103 104 VisitAllModules(visit func(Module)) 105 VisitAllModulesIf(pred func(Module) bool, visit func(Module)) 106 107 // Verify the make variable matches the Soong version, fail the build 108 // if it does not. If the make variable is empty, just set it. 109 Strict(name, ninjaStr string) 110 // Check to see if the make variable matches the Soong version, warn if 111 // it does not. If the make variable is empty, just set it. 112 Check(name, ninjaStr string) 113 114 // These are equivalent to the above, but sort the make and soong 115 // variables before comparing them. They also show the unique entries 116 // in each list when displaying the difference, instead of the entire 117 // string. 118 StrictSorted(name, ninjaStr string) 119 CheckSorted(name, ninjaStr string) 120 121 // Evaluates a ninja string and returns the result. Used if more 122 // complicated modification needs to happen before giving it to Make. 123 Eval(ninjaStr string) (string, error) 124} 125 126// MakeVarsModuleContext contains the set of functions available for modules 127// implementing the ModuleMakeVarsProvider interface. 128type MakeVarsModuleContext interface { 129 BaseMakeVarsContext 130} 131 132var _ PathContext = MakeVarsContext(nil) 133 134type MakeVarsProvider func(ctx MakeVarsContext) 135 136func RegisterMakeVarsProvider(pctx PackageContext, provider MakeVarsProvider) { 137 makeVarsInitProviders = append(makeVarsInitProviders, makeVarsProvider{pctx, provider}) 138} 139 140// SingletonMakeVarsProvider is a Singleton with an extra method to provide extra values to be exported to Make. 141type SingletonMakeVarsProvider interface { 142 // MakeVars uses a MakeVarsContext to provide extra values to be exported to Make. 143 MakeVars(ctx MakeVarsContext) 144} 145 146var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey") 147 148func getSingletonMakevarsProviders(config Config) *[]makeVarsProvider { 149 return config.Once(singletonMakeVarsProvidersKey, func() interface{} { 150 return &[]makeVarsProvider{} 151 }).(*[]makeVarsProvider) 152} 153 154// registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to 155// the list of MakeVarsProviders to run. 156func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) { 157 // Singletons are registered on the Context and may be different between different Contexts, 158 // for example when running multiple tests. Store the SingletonMakeVarsProviders in the 159 // Config so they are attached to the Context. 160 singletonMakeVarsProviders := getSingletonMakevarsProviders(config) 161 162 *singletonMakeVarsProviders = append(*singletonMakeVarsProviders, 163 makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)}) 164} 165 166// singletonMakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider. 167func singletonMakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider { 168 return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) } 169} 170 171// ModuleMakeVarsProvider is a Module with an extra method to provide extra values to be exported to Make. 172type ModuleMakeVarsProvider interface { 173 Module 174 175 // MakeVars uses a MakeVarsModuleContext to provide extra values to be exported to Make. 176 MakeVars(ctx MakeVarsModuleContext) 177} 178 179// ///////////////////////////////////////////////////////////////////////////// 180 181func makeVarsSingletonFunc() Singleton { 182 return &makeVarsSingleton{} 183} 184 185type makeVarsSingleton struct { 186 varsForTesting []makeVarsVariable 187 installsForTesting []byte 188} 189 190type makeVarsProvider struct { 191 pctx PackageContext 192 call MakeVarsProvider 193} 194 195// Collection of makevars providers that are registered in init() methods. 196var makeVarsInitProviders []makeVarsProvider 197 198type makeVarsContext struct { 199 SingletonContext 200 config Config 201 pctx PackageContext 202 vars []makeVarsVariable 203 phonies []phony 204 dists []dist 205} 206 207var _ MakeVarsContext = &makeVarsContext{} 208 209type makeVarsVariable struct { 210 name string 211 value string 212 sort bool 213 strict bool 214} 215 216type phony struct { 217 name string 218 deps []string 219} 220 221type dist struct { 222 goals []string 223 paths []string 224} 225 226func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) { 227 if !ctx.Config().KatiEnabled() { 228 return 229 } 230 231 outFile := absolutePath(PathForOutput(ctx, 232 "make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String()) 233 234 lateOutFile := absolutePath(PathForOutput(ctx, 235 "late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String()) 236 237 installsFile := absolutePath(PathForOutput(ctx, 238 "installs"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String()) 239 240 if ctx.Failed() { 241 return 242 } 243 244 var vars []makeVarsVariable 245 var dists []dist 246 var phonies []phony 247 var katiInstalls []katiInstall 248 var katiInitRcInstalls []katiInstall 249 var katiVintfManifestInstalls []katiInstall 250 var katiSymlinks []katiInstall 251 252 providers := append([]makeVarsProvider(nil), makeVarsInitProviders...) 253 providers = append(providers, *getSingletonMakevarsProviders(ctx.Config())...) 254 255 for _, provider := range providers { 256 mctx := &makeVarsContext{ 257 SingletonContext: ctx, 258 pctx: provider.pctx, 259 } 260 261 provider.call(mctx) 262 263 vars = append(vars, mctx.vars...) 264 phonies = append(phonies, mctx.phonies...) 265 dists = append(dists, mctx.dists...) 266 } 267 268 ctx.VisitAllModules(func(m Module) { 269 if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled(ctx) { 270 mctx := &makeVarsContext{ 271 SingletonContext: ctx, 272 } 273 274 provider.MakeVars(mctx) 275 276 vars = append(vars, mctx.vars...) 277 phonies = append(phonies, mctx.phonies...) 278 dists = append(dists, mctx.dists...) 279 } 280 281 if m.ExportedToMake() { 282 info := OtherModuleProviderOrDefault(ctx, m, InstallFilesProvider) 283 katiInstalls = append(katiInstalls, info.KatiInstalls...) 284 katiInitRcInstalls = append(katiInitRcInstalls, info.KatiInitRcInstalls...) 285 katiVintfManifestInstalls = append(katiVintfManifestInstalls, info.KatiVintfInstalls...) 286 katiSymlinks = append(katiSymlinks, info.KatiSymlinks...) 287 } 288 }) 289 290 compareKatiInstalls := func(a, b katiInstall) int { 291 aTo, bTo := a.to.String(), b.to.String() 292 if cmpTo := cmp.Compare(aTo, bTo); cmpTo != 0 { 293 return cmpTo 294 } 295 296 aFrom, bFrom := a.from.String(), b.from.String() 297 return cmp.Compare(aFrom, bFrom) 298 } 299 300 slices.SortFunc(katiInitRcInstalls, compareKatiInstalls) 301 katiInitRcInstalls = slices.CompactFunc(katiInitRcInstalls, func(a, b katiInstall) bool { 302 return compareKatiInstalls(a, b) == 0 303 }) 304 katiInstalls = append(katiInstalls, katiInitRcInstalls...) 305 306 slices.SortFunc(katiVintfManifestInstalls, compareKatiInstalls) 307 katiVintfManifestInstalls = slices.CompactFunc(katiVintfManifestInstalls, func(a, b katiInstall) bool { 308 return compareKatiInstalls(a, b) == 0 309 }) 310 311 if ctx.Failed() { 312 return 313 } 314 315 sort.Slice(vars, func(i, j int) bool { 316 return vars[i].name < vars[j].name 317 }) 318 sort.Slice(phonies, func(i, j int) bool { 319 return phonies[i].name < phonies[j].name 320 }) 321 lessArr := func(a, b []string) bool { 322 if len(a) == len(b) { 323 for i := range a { 324 if a[i] < b[i] { 325 return true 326 } 327 } 328 return false 329 } 330 return len(a) < len(b) 331 } 332 sort.Slice(dists, func(i, j int) bool { 333 return lessArr(dists[i].goals, dists[j].goals) || lessArr(dists[i].paths, dists[j].paths) 334 }) 335 336 outBytes := s.writeVars(vars) 337 338 if err := pathtools.WriteFileIfChanged(outFile, outBytes, 0666); err != nil { 339 ctx.Errorf(err.Error()) 340 } 341 342 lateOutBytes := s.writeLate(phonies, dists) 343 344 if err := pathtools.WriteFileIfChanged(lateOutFile, lateOutBytes, 0666); err != nil { 345 ctx.Errorf(err.Error()) 346 } 347 348 installsBytes := s.writeInstalls(katiInstalls, katiSymlinks, katiVintfManifestInstalls) 349 if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil { 350 ctx.Errorf(err.Error()) 351 } 352 353 // Only save state for tests when testing. 354 if ctx.Config().RunningInsideUnitTest() { 355 s.varsForTesting = vars 356 s.installsForTesting = installsBytes 357 } 358} 359 360func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte { 361 buf := &bytes.Buffer{} 362 363 fmt.Fprint(buf, `# Autogenerated file 364 365# Compares SOONG_$(1) against $(1), and warns if they are not equal. 366# 367# If the original variable is empty, then just set it to the SOONG_ version. 368# 369# $(1): Name of the variable to check 370# $(2): If not-empty, sort the values before comparing 371# $(3): Extra snippet to run if it does not match 372define soong-compare-var 373ifneq ($$($(1)),) 374 my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1)))) 375 my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1))) 376 ifneq ($$(my_val_make),$$(my_val_soong)) 377 $$(warning $(1) does not match between Make and Soong:) 378 $(if $(2),$$(warning Make adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make))) 379 $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong))) 380 $(3) 381 endif 382 my_val_make := 383 my_val_soong := 384else 385 $(1) := $$(SOONG_$(1)) 386endif 387.KATI_READONLY := $(1) SOONG_$(1) 388endef 389 390my_check_failed := false 391 392`) 393 394 // Write all the strict checks out first so that if one of them errors, 395 // we get all of the strict errors printed, but not the non-strict 396 // warnings. 397 for _, v := range vars { 398 if !v.strict { 399 continue 400 } 401 402 sort := "" 403 if v.sort { 404 sort = "true" 405 } 406 407 fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) 408 fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort) 409 } 410 411 fmt.Fprint(buf, ` 412ifneq ($(my_check_failed),false) 413 $(error Soong variable check failed) 414endif 415my_check_failed := 416 417 418`) 419 420 for _, v := range vars { 421 if v.strict { 422 continue 423 } 424 425 sort := "" 426 if v.sort { 427 sort = "true" 428 } 429 430 fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) 431 fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort) 432 } 433 434 fmt.Fprintln(buf, "\nsoong-compare-var :=") 435 436 fmt.Fprintln(buf) 437 438 return buf.Bytes() 439} 440 441func (s *makeVarsSingleton) writeLate(phonies []phony, dists []dist) []byte { 442 buf := &bytes.Buffer{} 443 444 fmt.Fprint(buf, `# Autogenerated file 445 446# Values written by Soong read after parsing all Android.mk files. 447 448 449`) 450 451 for _, phony := range phonies { 452 fmt.Fprintf(buf, ".PHONY: %s\n", phony.name) 453 fmt.Fprintf(buf, "%s: %s\n", phony.name, strings.Join(phony.deps, "\\\n ")) 454 } 455 456 fmt.Fprintln(buf) 457 458 for _, dist := range dists { 459 fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " ")) 460 fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n", 461 strings.Join(dist.goals, " "), strings.Join(dist.paths, " ")) 462 } 463 464 return buf.Bytes() 465} 466 467// writeInstalls writes the list of install rules generated by Soong to a makefile. The rules 468// are exported to Make instead of written directly to the ninja file so that main.mk can add 469// the dependencies from the `required` property that are hard to resolve in Soong. 470func (s *makeVarsSingleton) writeInstalls(installs, symlinks, katiVintfManifestInstalls []katiInstall) []byte { 471 buf := &bytes.Buffer{} 472 473 fmt.Fprint(buf, `# Autogenerated file 474 475# Values written by Soong to generate install rules that can be amended by Kati. 476 477EXTRA_INSTALL_ZIPS := 478`) 479 480 preserveSymlinksFlag := "-d" 481 if runtime.GOOS == "darwin" { 482 preserveSymlinksFlag = "-R" 483 } 484 485 for _, install := range installs { 486 // Write a rule for each install request in the form: 487 // to: from [ deps ] [ | order only deps ] 488 // cp -f -d $< $@ [ && chmod +x $@ ] 489 fmt.Fprintf(buf, "%s: %s", install.to.String(), install.from.String()) 490 for _, dep := range install.implicitDeps { 491 fmt.Fprintf(buf, " %s", dep.String()) 492 } 493 if extraFiles := install.extraFiles; extraFiles != nil { 494 fmt.Fprintf(buf, " %s", extraFiles.zip.String()) 495 } 496 if len(install.orderOnlyDeps) > 0 { 497 fmt.Fprintf(buf, " |") 498 } 499 for _, dep := range install.orderOnlyDeps { 500 fmt.Fprintf(buf, " %s", dep.String()) 501 } 502 fmt.Fprintln(buf) 503 fmt.Fprintln(buf, "\t@echo \"Install: $@\"") 504 fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@\n", preserveSymlinksFlag) 505 if install.executable { 506 fmt.Fprintf(buf, "\tchmod +x $@\n") 507 } 508 if extraFiles := install.extraFiles; extraFiles != nil { 509 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()) 510 fmt.Fprintf(buf, "\t ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )\n") 511 fmt.Fprintf(buf, "EXTRA_INSTALL_ZIPS += %s:%s:%s\n", install.to.String(), extraFiles.dir.String(), extraFiles.zip.String()) 512 } 513 514 fmt.Fprintln(buf) 515 } 516 fmt.Fprintf(buf, ".KATI_READONLY := EXTRA_INSTALL_ZIPS\n") 517 fmt.Fprintf(buf, "$(KATI_visibility_prefix EXTRA_INSTALL_ZIPS,build/make/core/Makefile)\n") 518 519 for _, symlink := range symlinks { 520 fmt.Fprintf(buf, "%s:", symlink.to.String()) 521 if symlink.from != nil { 522 // The katiVintfManifestInstall doesn't need updating when the target is modified, but we sometimes 523 // have a dependency on a katiVintfManifestInstall to a binary instead of to the binary directly, and 524 // the mtime of the katiVintfManifestInstall must be updated when the binary is modified, so use a 525 // normal dependency here instead of an order-only dependency. 526 fmt.Fprintf(buf, " %s", symlink.from.String()) 527 } 528 for _, dep := range symlink.implicitDeps { 529 fmt.Fprintf(buf, " %s", dep.String()) 530 } 531 if len(symlink.orderOnlyDeps) > 0 { 532 fmt.Fprintf(buf, " |") 533 } 534 for _, dep := range symlink.orderOnlyDeps { 535 fmt.Fprintf(buf, " %s", dep.String()) 536 } 537 fmt.Fprintln(buf) 538 539 fromStr := "" 540 if symlink.from != nil { 541 rel, err := filepath.Rel(filepath.Dir(symlink.to.String()), symlink.from.String()) 542 if err != nil { 543 panic(fmt.Errorf("failed to find relative path for katiVintfManifestInstall from %q to %q: %w", 544 symlink.from.String(), symlink.to.String(), err)) 545 } 546 fromStr = rel 547 } else { 548 fromStr = symlink.absFrom 549 } 550 551 fmt.Fprintln(buf, "\t@echo \"Symlink: $@\"") 552 fmt.Fprintf(buf, "\trm -f $@ && ln -sfn %s $@", fromStr) 553 fmt.Fprintln(buf) 554 fmt.Fprintln(buf) 555 } 556 557 for _, install := range katiVintfManifestInstalls { 558 // Write a rule for each vintf install request that calls the copy-vintf-manifest-chedk make function. 559 fmt.Fprintf(buf, "$(eval $(call copy-vintf-manifest-checked, %s, %s))\n", install.from.String(), install.to.String()) 560 561 if len(install.implicitDeps) > 0 { 562 panic(fmt.Errorf("unsupported implicitDeps %q in vintf install rule %q", install.implicitDeps, install.to)) 563 } 564 if len(install.orderOnlyDeps) > 0 { 565 panic(fmt.Errorf("unsupported orderOnlyDeps %q in vintf install rule %q", install.orderOnlyDeps, install.to)) 566 } 567 568 fmt.Fprintln(buf) 569 } 570 return buf.Bytes() 571} 572 573func (c *makeVarsContext) DeviceConfig() DeviceConfig { 574 return DeviceConfig{c.Config().deviceConfig} 575} 576 577var ninjaDescaper = strings.NewReplacer("$$", "$") 578 579func (c *makeVarsContext) Eval(ninjaStr string) (string, error) { 580 s, err := c.SingletonContext.Eval(c.pctx, ninjaStr) 581 if err != nil { 582 return "", err 583 } 584 // SingletonContext.Eval returns an exapnded string that is valid for a ninja file, de-escape $$ to $ for use 585 // in a Makefile 586 return ninjaDescaper.Replace(s), nil 587} 588 589func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) { 590 c.vars = append(c.vars, makeVarsVariable{ 591 name: name, 592 value: value, 593 strict: strict, 594 sort: sort, 595 }) 596} 597 598func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) { 599 value, err := c.Eval(ninjaStr) 600 if err != nil { 601 c.SingletonContext.Errorf(err.Error()) 602 } 603 c.addVariableRaw(name, value, strict, sort) 604} 605 606func (c *makeVarsContext) addPhony(name string, deps []string) { 607 c.phonies = append(c.phonies, phony{name, deps}) 608} 609 610func (c *makeVarsContext) addDist(goals []string, paths []string) { 611 c.dists = append(c.dists, dist{ 612 goals: goals, 613 paths: paths, 614 }) 615} 616 617func (c *makeVarsContext) Strict(name, ninjaStr string) { 618 c.addVariable(name, ninjaStr, true, false) 619} 620func (c *makeVarsContext) StrictSorted(name, ninjaStr string) { 621 c.addVariable(name, ninjaStr, true, true) 622} 623func (c *makeVarsContext) StrictRaw(name, value string) { 624 c.addVariableRaw(name, value, true, false) 625} 626 627func (c *makeVarsContext) Check(name, ninjaStr string) { 628 c.addVariable(name, ninjaStr, false, false) 629} 630func (c *makeVarsContext) CheckSorted(name, ninjaStr string) { 631 c.addVariable(name, ninjaStr, false, true) 632} 633func (c *makeVarsContext) CheckRaw(name, value string) { 634 c.addVariableRaw(name, value, false, false) 635} 636 637func (c *makeVarsContext) Phony(name string, deps ...Path) { 638 c.addPhony(name, Paths(deps).Strings()) 639} 640 641func (c *makeVarsContext) DistForGoal(goal string, paths ...Path) { 642 c.DistForGoals([]string{goal}, paths...) 643} 644 645func (c *makeVarsContext) DistForGoalWithFilename(goal string, path Path, filename string) { 646 c.DistForGoalsWithFilename([]string{goal}, path, filename) 647} 648 649func (c *makeVarsContext) DistForGoals(goals []string, paths ...Path) { 650 c.addDist(goals, Paths(paths).Strings()) 651} 652 653func (c *makeVarsContext) DistForGoalsWithFilename(goals []string, path Path, filename string) { 654 c.addDist(goals, []string{path.String() + ":" + filename}) 655} 656