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 moduleProvider(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 katiInstalls = append(katiInstalls, m.base().katiInstalls...) 283 katiInitRcInstalls = append(katiInitRcInstalls, m.base().katiInitRcInstalls...) 284 katiVintfManifestInstalls = append(katiVintfManifestInstalls, m.base().katiVintfInstalls...) 285 katiSymlinks = append(katiSymlinks, m.base().katiSymlinks...) 286 } 287 }) 288 289 compareKatiInstalls := func(a, b katiInstall) int { 290 aTo, bTo := a.to.String(), b.to.String() 291 if cmpTo := cmp.Compare(aTo, bTo); cmpTo != 0 { 292 return cmpTo 293 } 294 295 aFrom, bFrom := a.from.String(), b.from.String() 296 return cmp.Compare(aFrom, bFrom) 297 } 298 299 slices.SortFunc(katiInitRcInstalls, compareKatiInstalls) 300 katiInitRcInstalls = slices.CompactFunc(katiInitRcInstalls, func(a, b katiInstall) bool { 301 return compareKatiInstalls(a, b) == 0 302 }) 303 katiInstalls = append(katiInstalls, katiInitRcInstalls...) 304 305 slices.SortFunc(katiVintfManifestInstalls, compareKatiInstalls) 306 katiVintfManifestInstalls = slices.CompactFunc(katiVintfManifestInstalls, func(a, b katiInstall) bool { 307 return compareKatiInstalls(a, b) == 0 308 }) 309 310 if ctx.Failed() { 311 return 312 } 313 314 sort.Slice(vars, func(i, j int) bool { 315 return vars[i].name < vars[j].name 316 }) 317 sort.Slice(phonies, func(i, j int) bool { 318 return phonies[i].name < phonies[j].name 319 }) 320 lessArr := func(a, b []string) bool { 321 if len(a) == len(b) { 322 for i := range a { 323 if a[i] < b[i] { 324 return true 325 } 326 } 327 return false 328 } 329 return len(a) < len(b) 330 } 331 sort.Slice(dists, func(i, j int) bool { 332 return lessArr(dists[i].goals, dists[j].goals) || lessArr(dists[i].paths, dists[j].paths) 333 }) 334 335 outBytes := s.writeVars(vars) 336 337 if err := pathtools.WriteFileIfChanged(outFile, outBytes, 0666); err != nil { 338 ctx.Errorf(err.Error()) 339 } 340 341 lateOutBytes := s.writeLate(phonies, dists) 342 343 if err := pathtools.WriteFileIfChanged(lateOutFile, lateOutBytes, 0666); err != nil { 344 ctx.Errorf(err.Error()) 345 } 346 347 installsBytes := s.writeInstalls(katiInstalls, katiSymlinks, katiVintfManifestInstalls) 348 if err := pathtools.WriteFileIfChanged(installsFile, installsBytes, 0666); err != nil { 349 ctx.Errorf(err.Error()) 350 } 351 352 // Only save state for tests when testing. 353 if ctx.Config().RunningInsideUnitTest() { 354 s.varsForTesting = vars 355 s.installsForTesting = installsBytes 356 } 357} 358 359func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte { 360 buf := &bytes.Buffer{} 361 362 fmt.Fprint(buf, `# Autogenerated file 363 364# Compares SOONG_$(1) against $(1), and warns if they are not equal. 365# 366# If the original variable is empty, then just set it to the SOONG_ version. 367# 368# $(1): Name of the variable to check 369# $(2): If not-empty, sort the values before comparing 370# $(3): Extra snippet to run if it does not match 371define soong-compare-var 372ifneq ($$($(1)),) 373 my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1)))) 374 my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1))) 375 ifneq ($$(my_val_make),$$(my_val_soong)) 376 $$(warning $(1) does not match between Make and Soong:) 377 $(if $(2),$$(warning Make adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make))) 378 $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong))) 379 $(3) 380 endif 381 my_val_make := 382 my_val_soong := 383else 384 $(1) := $$(SOONG_$(1)) 385endif 386.KATI_READONLY := $(1) SOONG_$(1) 387endef 388 389my_check_failed := false 390 391`) 392 393 // Write all the strict checks out first so that if one of them errors, 394 // we get all of the strict errors printed, but not the non-strict 395 // warnings. 396 for _, v := range vars { 397 if !v.strict { 398 continue 399 } 400 401 sort := "" 402 if v.sort { 403 sort = "true" 404 } 405 406 fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) 407 fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort) 408 } 409 410 fmt.Fprint(buf, ` 411ifneq ($(my_check_failed),false) 412 $(error Soong variable check failed) 413endif 414my_check_failed := 415 416 417`) 418 419 for _, v := range vars { 420 if v.strict { 421 continue 422 } 423 424 sort := "" 425 if v.sort { 426 sort = "true" 427 } 428 429 fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value) 430 fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort) 431 } 432 433 fmt.Fprintln(buf, "\nsoong-compare-var :=") 434 435 fmt.Fprintln(buf) 436 437 return buf.Bytes() 438} 439 440func (s *makeVarsSingleton) writeLate(phonies []phony, dists []dist) []byte { 441 buf := &bytes.Buffer{} 442 443 fmt.Fprint(buf, `# Autogenerated file 444 445# Values written by Soong read after parsing all Android.mk files. 446 447 448`) 449 450 for _, phony := range phonies { 451 fmt.Fprintf(buf, ".PHONY: %s\n", phony.name) 452 fmt.Fprintf(buf, "%s: %s\n", phony.name, strings.Join(phony.deps, "\\\n ")) 453 } 454 455 fmt.Fprintln(buf) 456 457 for _, dist := range dists { 458 fmt.Fprintf(buf, ".PHONY: %s\n", strings.Join(dist.goals, " ")) 459 fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n", 460 strings.Join(dist.goals, " "), strings.Join(dist.paths, " ")) 461 } 462 463 return buf.Bytes() 464} 465 466// writeInstalls writes the list of install rules generated by Soong to a makefile. The rules 467// are exported to Make instead of written directly to the ninja file so that main.mk can add 468// the dependencies from the `required` property that are hard to resolve in Soong. 469func (s *makeVarsSingleton) writeInstalls(installs, symlinks, katiVintfManifestInstalls []katiInstall) []byte { 470 buf := &bytes.Buffer{} 471 472 fmt.Fprint(buf, `# Autogenerated file 473 474# Values written by Soong to generate install rules that can be amended by Kati. 475 476EXTRA_INSTALL_ZIPS := 477`) 478 479 preserveSymlinksFlag := "-d" 480 if runtime.GOOS == "darwin" { 481 preserveSymlinksFlag = "-R" 482 } 483 484 for _, install := range installs { 485 // Write a rule for each install request in the form: 486 // to: from [ deps ] [ | order only deps ] 487 // cp -f -d $< $@ [ && chmod +x $@ ] 488 fmt.Fprintf(buf, "%s: %s", install.to.String(), install.from.String()) 489 for _, dep := range install.implicitDeps { 490 fmt.Fprintf(buf, " %s", dep.String()) 491 } 492 if extraFiles := install.extraFiles; extraFiles != nil { 493 fmt.Fprintf(buf, " %s", extraFiles.zip.String()) 494 } 495 if len(install.orderOnlyDeps) > 0 { 496 fmt.Fprintf(buf, " |") 497 } 498 for _, dep := range install.orderOnlyDeps { 499 fmt.Fprintf(buf, " %s", dep.String()) 500 } 501 fmt.Fprintln(buf) 502 fmt.Fprintln(buf, "\t@echo \"Install: $@\"") 503 fmt.Fprintf(buf, "\trm -f $@ && cp -f %s $< $@\n", preserveSymlinksFlag) 504 if install.executable { 505 fmt.Fprintf(buf, "\tchmod +x $@\n") 506 } 507 if extraFiles := install.extraFiles; extraFiles != nil { 508 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()) 509 fmt.Fprintf(buf, "\t ( code=$$?; if [ $$code -ne 0 -a $$code -ne 1 ]; then exit $$code; fi )\n") 510 fmt.Fprintf(buf, "EXTRA_INSTALL_ZIPS += %s:%s:%s\n", install.to.String(), extraFiles.dir.String(), extraFiles.zip.String()) 511 } 512 513 fmt.Fprintln(buf) 514 } 515 fmt.Fprintf(buf, ".KATI_READONLY := EXTRA_INSTALL_ZIPS\n") 516 fmt.Fprintf(buf, "$(KATI_visibility_prefix EXTRA_INSTALL_ZIPS,build/make/core/Makefile)\n") 517 518 for _, symlink := range symlinks { 519 fmt.Fprintf(buf, "%s:", symlink.to.String()) 520 if symlink.from != nil { 521 // The katiVintfManifestInstall doesn't need updating when the target is modified, but we sometimes 522 // have a dependency on a katiVintfManifestInstall to a binary instead of to the binary directly, and 523 // the mtime of the katiVintfManifestInstall must be updated when the binary is modified, so use a 524 // normal dependency here instead of an order-only dependency. 525 fmt.Fprintf(buf, " %s", symlink.from.String()) 526 } 527 for _, dep := range symlink.implicitDeps { 528 fmt.Fprintf(buf, " %s", dep.String()) 529 } 530 if len(symlink.orderOnlyDeps) > 0 { 531 fmt.Fprintf(buf, " |") 532 } 533 for _, dep := range symlink.orderOnlyDeps { 534 fmt.Fprintf(buf, " %s", dep.String()) 535 } 536 fmt.Fprintln(buf) 537 538 fromStr := "" 539 if symlink.from != nil { 540 rel, err := filepath.Rel(filepath.Dir(symlink.to.String()), symlink.from.String()) 541 if err != nil { 542 panic(fmt.Errorf("failed to find relative path for katiVintfManifestInstall from %q to %q: %w", 543 symlink.from.String(), symlink.to.String(), err)) 544 } 545 fromStr = rel 546 } else { 547 fromStr = symlink.absFrom 548 } 549 550 fmt.Fprintln(buf, "\t@echo \"Symlink: $@\"") 551 fmt.Fprintf(buf, "\trm -f $@ && ln -sfn %s $@", fromStr) 552 fmt.Fprintln(buf) 553 fmt.Fprintln(buf) 554 } 555 556 for _, install := range katiVintfManifestInstalls { 557 // Write a rule for each vintf install request that calls the copy-vintf-manifest-chedk make function. 558 fmt.Fprintf(buf, "$(eval $(call copy-vintf-manifest-checked, %s, %s))\n", install.from.String(), install.to.String()) 559 560 if len(install.implicitDeps) > 0 { 561 panic(fmt.Errorf("unsupported implicitDeps %q in vintf install rule %q", install.implicitDeps, install.to)) 562 } 563 if len(install.orderOnlyDeps) > 0 { 564 panic(fmt.Errorf("unsupported orderOnlyDeps %q in vintf install rule %q", install.orderOnlyDeps, install.to)) 565 } 566 567 fmt.Fprintln(buf) 568 } 569 return buf.Bytes() 570} 571 572func (c *makeVarsContext) DeviceConfig() DeviceConfig { 573 return DeviceConfig{c.Config().deviceConfig} 574} 575 576var ninjaDescaper = strings.NewReplacer("$$", "$") 577 578func (c *makeVarsContext) Eval(ninjaStr string) (string, error) { 579 s, err := c.SingletonContext.Eval(c.pctx, ninjaStr) 580 if err != nil { 581 return "", err 582 } 583 // SingletonContext.Eval returns an exapnded string that is valid for a ninja file, de-escape $$ to $ for use 584 // in a Makefile 585 return ninjaDescaper.Replace(s), nil 586} 587 588func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) { 589 c.vars = append(c.vars, makeVarsVariable{ 590 name: name, 591 value: value, 592 strict: strict, 593 sort: sort, 594 }) 595} 596 597func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) { 598 value, err := c.Eval(ninjaStr) 599 if err != nil { 600 c.SingletonContext.Errorf(err.Error()) 601 } 602 c.addVariableRaw(name, value, strict, sort) 603} 604 605func (c *makeVarsContext) addPhony(name string, deps []string) { 606 c.phonies = append(c.phonies, phony{name, deps}) 607} 608 609func (c *makeVarsContext) addDist(goals []string, paths []string) { 610 c.dists = append(c.dists, dist{ 611 goals: goals, 612 paths: paths, 613 }) 614} 615 616func (c *makeVarsContext) Strict(name, ninjaStr string) { 617 c.addVariable(name, ninjaStr, true, false) 618} 619func (c *makeVarsContext) StrictSorted(name, ninjaStr string) { 620 c.addVariable(name, ninjaStr, true, true) 621} 622func (c *makeVarsContext) StrictRaw(name, value string) { 623 c.addVariableRaw(name, value, true, false) 624} 625 626func (c *makeVarsContext) Check(name, ninjaStr string) { 627 c.addVariable(name, ninjaStr, false, false) 628} 629func (c *makeVarsContext) CheckSorted(name, ninjaStr string) { 630 c.addVariable(name, ninjaStr, false, true) 631} 632func (c *makeVarsContext) CheckRaw(name, value string) { 633 c.addVariableRaw(name, value, false, false) 634} 635 636func (c *makeVarsContext) Phony(name string, deps ...Path) { 637 c.addPhony(name, Paths(deps).Strings()) 638} 639 640func (c *makeVarsContext) DistForGoal(goal string, paths ...Path) { 641 c.DistForGoals([]string{goal}, paths...) 642} 643 644func (c *makeVarsContext) DistForGoalWithFilename(goal string, path Path, filename string) { 645 c.DistForGoalsWithFilename([]string{goal}, path, filename) 646} 647 648func (c *makeVarsContext) DistForGoals(goals []string, paths ...Path) { 649 c.addDist(goals, Paths(paths).Strings()) 650} 651 652func (c *makeVarsContext) DistForGoalsWithFilename(goals []string, path Path, filename string) { 653 c.addDist(goals, []string{path.String() + ":" + filename}) 654} 655