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