blob: 40c0ccded04dfb05ef5d5bc439b9d6c20cb9c4a5 [file] [log] [blame]
Dan Willemsen4b7d5de2016-01-12 23:20:28 -08001// 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
Colin Cross635c3b02016-05-18 15:37:25 -070015package android
Dan Willemsen4b7d5de2016-01-12 23:20:28 -080016
17import (
18 "bytes"
19 "fmt"
Dan Willemsen6a6478d2020-07-17 19:28:53 -070020 "sort"
Colin Cross31656952018-05-24 16:11:20 -070021 "strings"
Dan Willemsen4b7d5de2016-01-12 23:20:28 -080022
Colin Cross65494b92019-02-07 14:25:51 -080023 "github.com/google/blueprint"
Colin Crossc3d87d32020-06-04 13:25:17 -070024 "github.com/google/blueprint/pathtools"
Dan Willemsen4b7d5de2016-01-12 23:20:28 -080025 "github.com/google/blueprint/proptools"
26)
27
Dan Albertf5415d72017-08-17 16:19:59 -070028func init() {
29 RegisterMakeVarsProvider(pctx, androidMakeVarsProvider)
30}
31
32func androidMakeVarsProvider(ctx MakeVarsContext) {
Dan Albert1a246272020-07-06 14:49:35 -070033 ctx.Strict("MIN_SUPPORTED_SDK_VERSION", ctx.Config().MinSupportedSdkVersion().String())
Dan Albertf5415d72017-08-17 16:19:59 -070034}
35
Dan Willemsen4b7d5de2016-01-12 23:20:28 -080036///////////////////////////////////////////////////////////////////////////////
Dan Willemsen6a6478d2020-07-17 19:28:53 -070037
38// BaseMakeVarsContext contains the common functions for other packages to use
39// to declare make variables
40type BaseMakeVarsContext interface {
Dan Willemsen4b7d5de2016-01-12 23:20:28 -080041 Config() Config
Dan Willemsen3fb1fae2018-03-12 15:30:26 -070042 DeviceConfig() DeviceConfig
Colin Cross65494b92019-02-07 14:25:51 -080043 AddNinjaFileDeps(deps ...string)
Colin Cross65494b92019-02-07 14:25:51 -080044
Colin Cross65494b92019-02-07 14:25:51 -080045 Failed() bool
46
Dan Willemsen558e5172016-05-19 16:58:46 -070047 // These are equivalent to Strict and Check, but do not attempt to
48 // evaluate the values before writing them to the Makefile. They can
49 // be used when all ninja variables have already been evaluated through
50 // Eval().
51 StrictRaw(name, value string)
52 CheckRaw(name, value string)
Colin Cross8177ad22019-11-04 10:27:48 -080053
54 // GlobWithDeps returns a list of files that match the specified pattern but do not match any
55 // of the patterns in excludes. It also adds efficient dependencies to rerun the primary
56 // builder whenever a file matching the pattern as added or removed, without rerunning if a
57 // file that does not match the pattern is added to a searched directory.
58 GlobWithDeps(pattern string, excludes []string) ([]string, error)
Colin Crossc3d87d32020-06-04 13:25:17 -070059
60 // Phony creates a phony rule in Make, which will allow additional DistForGoal
61 // dependencies to be added to it. Phony can be called on the same name multiple
62 // times to add additional dependencies.
63 Phony(names string, deps ...Path)
Colin Cross3cda0d82019-09-24 13:40:07 -070064
65 // DistForGoal creates a rule to copy one or more Paths to the artifacts
66 // directory on the build server when the specified goal is built.
67 DistForGoal(goal string, paths ...Path)
68
69 // DistForGoalWithFilename creates a rule to copy a Path to the artifacts
70 // directory on the build server with the given filename when the specified
71 // goal is built.
72 DistForGoalWithFilename(goal string, path Path, filename string)
73
74 // DistForGoals creates a rule to copy one or more Paths to the artifacts
75 // directory on the build server when any of the specified goals are built.
76 DistForGoals(goals []string, paths ...Path)
77
78 // DistForGoalsWithFilename creates a rule to copy a Path to the artifacts
79 // directory on the build server with the given filename when any of the
80 // specified goals are built.
81 DistForGoalsWithFilename(goals []string, path Path, filename string)
Dan Willemsen4b7d5de2016-01-12 23:20:28 -080082}
83
Dan Willemsen6a6478d2020-07-17 19:28:53 -070084// MakeVarsContext contains the set of functions available for MakeVarsProvider
85// and SingletonMakeVarsProvider implementations.
86type MakeVarsContext interface {
87 BaseMakeVarsContext
88
89 ModuleName(module blueprint.Module) string
90 ModuleDir(module blueprint.Module) string
91 ModuleSubDir(module blueprint.Module) string
92 ModuleType(module blueprint.Module) string
Colin Cross127bb8b2020-12-16 16:46:01 -080093 ModuleProvider(module blueprint.Module, key blueprint.ProviderKey) interface{}
Dan Willemsen6a6478d2020-07-17 19:28:53 -070094 BlueprintFile(module blueprint.Module) string
95
96 ModuleErrorf(module blueprint.Module, format string, args ...interface{})
97 Errorf(format string, args ...interface{})
98
99 VisitAllModules(visit func(Module))
100 VisitAllModulesIf(pred func(Module) bool, visit func(Module))
101
102 // Verify the make variable matches the Soong version, fail the build
103 // if it does not. If the make variable is empty, just set it.
104 Strict(name, ninjaStr string)
105 // Check to see if the make variable matches the Soong version, warn if
106 // it does not. If the make variable is empty, just set it.
107 Check(name, ninjaStr string)
108
109 // These are equivalent to the above, but sort the make and soong
110 // variables before comparing them. They also show the unique entries
111 // in each list when displaying the difference, instead of the entire
112 // string.
113 StrictSorted(name, ninjaStr string)
114 CheckSorted(name, ninjaStr string)
115
116 // Evaluates a ninja string and returns the result. Used if more
117 // complicated modification needs to happen before giving it to Make.
118 Eval(ninjaStr string) (string, error)
119}
120
121// MakeVarsModuleContext contains the set of functions available for modules
122// implementing the ModuleMakeVarsProvider interface.
123type MakeVarsModuleContext interface {
124 BaseMakeVarsContext
125}
126
Colin Cross65494b92019-02-07 14:25:51 -0800127var _ PathContext = MakeVarsContext(nil)
128
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800129type MakeVarsProvider func(ctx MakeVarsContext)
130
Colin Cross0875c522017-11-28 17:34:01 -0800131func RegisterMakeVarsProvider(pctx PackageContext, provider MakeVarsProvider) {
Chris Parsonsf3c96ef2020-09-29 02:23:17 -0400132 makeVarsInitProviders = append(makeVarsInitProviders, makeVarsProvider{pctx, provider})
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800133}
134
Colin Crossed023ec2019-02-19 12:38:45 -0800135// SingletonMakeVarsProvider is a Singleton with an extra method to provide extra values to be exported to Make.
136type SingletonMakeVarsProvider interface {
Colin Crossed023ec2019-02-19 12:38:45 -0800137 // MakeVars uses a MakeVarsContext to provide extra values to be exported to Make.
138 MakeVars(ctx MakeVarsContext)
139}
140
Colin Cross06fa5882020-10-29 18:21:38 -0700141var singletonMakeVarsProvidersKey = NewOnceKey("singletonMakeVarsProvidersKey")
142
143// registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to
144// the list of MakeVarsProviders to run.
145func registerSingletonMakeVarsProvider(config Config, singleton SingletonMakeVarsProvider) {
146 // Singletons are registered on the Context and may be different between different Contexts,
147 // for example when running multiple tests. Store the SingletonMakeVarsProviders in the
148 // Config so they are attached to the Context.
149 singletonMakeVarsProviders := config.Once(singletonMakeVarsProvidersKey, func() interface{} {
150 return &[]makeVarsProvider{}
151 }).(*[]makeVarsProvider)
152
153 *singletonMakeVarsProviders = append(*singletonMakeVarsProviders,
154 makeVarsProvider{pctx, singletonMakeVarsProviderAdapter(singleton)})
Colin Crossed023ec2019-02-19 12:38:45 -0800155}
156
Colin Cross06fa5882020-10-29 18:21:38 -0700157// singletonMakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
158func singletonMakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
Colin Crossed023ec2019-02-19 12:38:45 -0800159 return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) }
160}
161
Dan Willemsen6a6478d2020-07-17 19:28:53 -0700162// ModuleMakeVarsProvider is a Module with an extra method to provide extra values to be exported to Make.
163type ModuleMakeVarsProvider interface {
164 Module
165
166 // MakeVars uses a MakeVarsModuleContext to provide extra values to be exported to Make.
167 MakeVars(ctx MakeVarsModuleContext)
168}
169
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800170///////////////////////////////////////////////////////////////////////////////
171
Colin Cross0875c522017-11-28 17:34:01 -0800172func makeVarsSingletonFunc() Singleton {
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800173 return &makeVarsSingleton{}
174}
175
176type makeVarsSingleton struct{}
177
178type makeVarsProvider struct {
Colin Cross0875c522017-11-28 17:34:01 -0800179 pctx PackageContext
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800180 call MakeVarsProvider
181}
182
Chris Parsonsf3c96ef2020-09-29 02:23:17 -0400183// Collection of makevars providers that are registered in init() methods.
184var makeVarsInitProviders []makeVarsProvider
185
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800186type makeVarsContext struct {
Colin Cross65494b92019-02-07 14:25:51 -0800187 SingletonContext
Colin Crossc3d87d32020-06-04 13:25:17 -0700188 config Config
189 pctx PackageContext
190 vars []makeVarsVariable
191 phonies []phony
Colin Cross3cda0d82019-09-24 13:40:07 -0700192 dists []dist
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800193}
194
195var _ MakeVarsContext = &makeVarsContext{}
196
197type makeVarsVariable struct {
198 name string
199 value string
200 sort bool
201 strict bool
202}
203
Colin Crossc3d87d32020-06-04 13:25:17 -0700204type phony struct {
205 name string
206 deps []string
207}
208
Colin Cross3cda0d82019-09-24 13:40:07 -0700209type dist struct {
210 goals []string
211 paths []string
212}
213
Colin Cross0875c522017-11-28 17:34:01 -0800214func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) {
Jingwen Chencda22c92020-11-23 00:22:30 -0500215 if !ctx.Config().KatiEnabled() {
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800216 return
217 }
218
Colin Cross988414c2020-01-11 01:11:46 +0000219 outFile := absolutePath(PathForOutput(ctx,
220 "make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800221
Colin Crossc3d87d32020-06-04 13:25:17 -0700222 lateOutFile := absolutePath(PathForOutput(ctx,
223 "late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
224
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800225 if ctx.Failed() {
226 return
227 }
228
Colin Cross3cda0d82019-09-24 13:40:07 -0700229 var vars []makeVarsVariable
230 var dists []dist
Colin Crossc3d87d32020-06-04 13:25:17 -0700231 var phonies []phony
Colin Cross06fa5882020-10-29 18:21:38 -0700232
233 providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
234 providers = append(providers, *ctx.Config().Get(singletonMakeVarsProvidersKey).(*[]makeVarsProvider)...)
235
236 for _, provider := range providers {
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800237 mctx := &makeVarsContext{
Colin Cross65494b92019-02-07 14:25:51 -0800238 SingletonContext: ctx,
239 pctx: provider.pctx,
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800240 }
241
242 provider.call(mctx)
243
244 vars = append(vars, mctx.vars...)
Colin Crossc3d87d32020-06-04 13:25:17 -0700245 phonies = append(phonies, mctx.phonies...)
Colin Cross3cda0d82019-09-24 13:40:07 -0700246 dists = append(dists, mctx.dists...)
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800247 }
248
Dan Willemsen6a6478d2020-07-17 19:28:53 -0700249 ctx.VisitAllModules(func(m Module) {
Jiyong Parkf78531b2020-09-09 17:14:28 +0900250 if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled() {
Dan Willemsen6a6478d2020-07-17 19:28:53 -0700251 mctx := &makeVarsContext{
252 SingletonContext: ctx,
253 }
254
255 provider.MakeVars(mctx)
256
257 vars = append(vars, mctx.vars...)
258 phonies = append(phonies, mctx.phonies...)
259 dists = append(dists, mctx.dists...)
260 }
261 })
262
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800263 if ctx.Failed() {
264 return
265 }
266
Dan Willemsen6a6478d2020-07-17 19:28:53 -0700267 sort.Slice(vars, func(i, j int) bool {
268 return vars[i].name < vars[j].name
269 })
270 sort.Slice(phonies, func(i, j int) bool {
271 return phonies[i].name < phonies[j].name
272 })
273 lessArr := func(a, b []string) bool {
274 if len(a) == len(b) {
275 for i := range a {
276 if a[i] < b[i] {
277 return true
278 }
279 }
280 return false
281 }
282 return len(a) < len(b)
283 }
284 sort.Slice(dists, func(i, j int) bool {
285 return lessArr(dists[i].goals, dists[j].goals) || lessArr(dists[i].paths, dists[j].paths)
286 })
287
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800288 outBytes := s.writeVars(vars)
289
Colin Crossc3d87d32020-06-04 13:25:17 -0700290 if err := pathtools.WriteFileIfChanged(outFile, outBytes, 0666); err != nil {
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800291 ctx.Errorf(err.Error())
292 }
Colin Crossc3d87d32020-06-04 13:25:17 -0700293
Colin Cross3cda0d82019-09-24 13:40:07 -0700294 lateOutBytes := s.writeLate(phonies, dists)
Colin Crossc3d87d32020-06-04 13:25:17 -0700295
296 if err := pathtools.WriteFileIfChanged(lateOutFile, lateOutBytes, 0666); err != nil {
297 ctx.Errorf(err.Error())
298 }
299
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800300}
301
302func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte {
303 buf := &bytes.Buffer{}
304
Dan Willemsen59339a22018-07-22 21:18:45 -0700305 fmt.Fprint(buf, `# Autogenerated file
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800306
307# Compares SOONG_$(1) against $(1), and warns if they are not equal.
308#
309# If the original variable is empty, then just set it to the SOONG_ version.
310#
311# $(1): Name of the variable to check
312# $(2): If not-empty, sort the values before comparing
313# $(3): Extra snippet to run if it does not match
314define soong-compare-var
315ifneq ($$($(1)),)
Dan Willemsen558e5172016-05-19 16:58:46 -0700316 my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1))))
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800317 my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1)))
318 ifneq ($$(my_val_make),$$(my_val_soong))
319 $$(warning $(1) does not match between Make and Soong:)
320 $(if $(2),$$(warning Make adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make)))
321 $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong)))
322 $(3)
323 endif
324 my_val_make :=
325 my_val_soong :=
326else
327 $(1) := $$(SOONG_$(1))
328endif
Dan Willemsende18f472016-09-30 10:16:38 -0700329.KATI_READONLY := $(1) SOONG_$(1)
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800330endef
331
332my_check_failed := false
333
334`)
335
336 // Write all the strict checks out first so that if one of them errors,
337 // we get all of the strict errors printed, but not the non-strict
338 // warnings.
339 for _, v := range vars {
340 if !v.strict {
341 continue
342 }
343
344 sort := ""
345 if v.sort {
346 sort = "true"
347 }
348
349 fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
350 fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort)
351 }
352
Dan Willemsen59339a22018-07-22 21:18:45 -0700353 fmt.Fprint(buf, `
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800354ifneq ($(my_check_failed),false)
355 $(error Soong variable check failed)
356endif
357my_check_failed :=
358
359
360`)
361
362 for _, v := range vars {
363 if v.strict {
364 continue
365 }
366
367 sort := ""
368 if v.sort {
369 sort = "true"
370 }
371
372 fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
373 fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort)
374 }
375
376 fmt.Fprintln(buf, "\nsoong-compare-var :=")
377
Colin Crossc3d87d32020-06-04 13:25:17 -0700378 fmt.Fprintln(buf)
379
380 return buf.Bytes()
381}
382
Colin Cross3cda0d82019-09-24 13:40:07 -0700383func (s *makeVarsSingleton) writeLate(phonies []phony, dists []dist) []byte {
Colin Crossc3d87d32020-06-04 13:25:17 -0700384 buf := &bytes.Buffer{}
385
386 fmt.Fprint(buf, `# Autogenerated file
387
388# Values written by Soong read after parsing all Android.mk files.
389
390
391`)
392
393 for _, phony := range phonies {
394 fmt.Fprintf(buf, ".PHONY: %s\n", phony.name)
395 fmt.Fprintf(buf, "%s: %s\n", phony.name, strings.Join(phony.deps, "\\\n "))
396 }
397
Colin Cross3cda0d82019-09-24 13:40:07 -0700398 fmt.Fprintln(buf)
399
400 for _, dist := range dists {
401 fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n",
402 strings.Join(dist.goals, " "), strings.Join(dist.paths, " "))
403 }
404
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800405 return buf.Bytes()
406}
407
Dan Willemsen3fb1fae2018-03-12 15:30:26 -0700408func (c *makeVarsContext) DeviceConfig() DeviceConfig {
Colin Cross65494b92019-02-07 14:25:51 -0800409 return DeviceConfig{c.Config().deviceConfig}
Jiyong Park374510b2018-03-19 18:23:01 +0900410}
411
Colin Cross31656952018-05-24 16:11:20 -0700412var ninjaDescaper = strings.NewReplacer("$$", "$")
413
Dan Willemsen558e5172016-05-19 16:58:46 -0700414func (c *makeVarsContext) Eval(ninjaStr string) (string, error) {
Colin Cross65494b92019-02-07 14:25:51 -0800415 s, err := c.SingletonContext.Eval(c.pctx, ninjaStr)
Colin Cross31656952018-05-24 16:11:20 -0700416 if err != nil {
417 return "", err
418 }
419 // SingletonContext.Eval returns an exapnded string that is valid for a ninja file, de-escape $$ to $ for use
420 // in a Makefile
421 return ninjaDescaper.Replace(s), nil
Dan Willemsen558e5172016-05-19 16:58:46 -0700422}
423
424func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) {
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800425 c.vars = append(c.vars, makeVarsVariable{
426 name: name,
427 value: value,
428 strict: strict,
429 sort: sort,
430 })
431}
432
Dan Willemsen558e5172016-05-19 16:58:46 -0700433func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) {
434 value, err := c.Eval(ninjaStr)
435 if err != nil {
Colin Cross65494b92019-02-07 14:25:51 -0800436 c.SingletonContext.Errorf(err.Error())
Dan Willemsen558e5172016-05-19 16:58:46 -0700437 }
438 c.addVariableRaw(name, value, strict, sort)
439}
440
Colin Crossc3d87d32020-06-04 13:25:17 -0700441func (c *makeVarsContext) addPhony(name string, deps []string) {
442 c.phonies = append(c.phonies, phony{name, deps})
443}
444
Colin Cross3cda0d82019-09-24 13:40:07 -0700445func (c *makeVarsContext) addDist(goals []string, paths []string) {
446 c.dists = append(c.dists, dist{
447 goals: goals,
448 paths: paths,
449 })
450}
451
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800452func (c *makeVarsContext) Strict(name, ninjaStr string) {
453 c.addVariable(name, ninjaStr, true, false)
454}
455func (c *makeVarsContext) StrictSorted(name, ninjaStr string) {
456 c.addVariable(name, ninjaStr, true, true)
457}
Dan Willemsen558e5172016-05-19 16:58:46 -0700458func (c *makeVarsContext) StrictRaw(name, value string) {
459 c.addVariableRaw(name, value, true, false)
460}
Dan Willemsen4b7d5de2016-01-12 23:20:28 -0800461
462func (c *makeVarsContext) Check(name, ninjaStr string) {
463 c.addVariable(name, ninjaStr, false, false)
464}
465func (c *makeVarsContext) CheckSorted(name, ninjaStr string) {
466 c.addVariable(name, ninjaStr, false, true)
467}
Dan Willemsen558e5172016-05-19 16:58:46 -0700468func (c *makeVarsContext) CheckRaw(name, value string) {
469 c.addVariableRaw(name, value, false, false)
470}
Colin Crossc3d87d32020-06-04 13:25:17 -0700471
472func (c *makeVarsContext) Phony(name string, deps ...Path) {
473 c.addPhony(name, Paths(deps).Strings())
474}
Colin Cross3cda0d82019-09-24 13:40:07 -0700475
476func (c *makeVarsContext) DistForGoal(goal string, paths ...Path) {
477 c.DistForGoals([]string{goal}, paths...)
478}
479
480func (c *makeVarsContext) DistForGoalWithFilename(goal string, path Path, filename string) {
481 c.DistForGoalsWithFilename([]string{goal}, path, filename)
482}
483
484func (c *makeVarsContext) DistForGoals(goals []string, paths ...Path) {
485 c.addDist(goals, Paths(paths).Strings())
486}
487
488func (c *makeVarsContext) DistForGoalsWithFilename(goals []string, path Path, filename string) {
489 c.addDist(goals, []string{path.String() + ":" + filename})
490}