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	"fmt"
20	"sort"
21	"strings"
22
23	"github.com/google/blueprint"
24	"github.com/google/blueprint/pathtools"
25	"github.com/google/blueprint/proptools"
26)
27
28func init() {
29	RegisterMakeVarsProvider(pctx, androidMakeVarsProvider)
30}
31
32func androidMakeVarsProvider(ctx MakeVarsContext) {
33	ctx.Strict("MIN_SUPPORTED_SDK_VERSION", ctx.Config().MinSupportedSdkVersion().String())
34}
35
36///////////////////////////////////////////////////////////////////////////////
37
38// BaseMakeVarsContext contains the common functions for other packages to use
39// to declare make variables
40type BaseMakeVarsContext interface {
41	Config() Config
42	DeviceConfig() DeviceConfig
43	AddNinjaFileDeps(deps ...string)
44
45	Failed() bool
46
47	// 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)
53
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)
59
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)
64
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)
82}
83
84// 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
93	ModuleProvider(module blueprint.Module, key blueprint.ProviderKey) interface{}
94	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
127var _ PathContext = MakeVarsContext(nil)
128
129type MakeVarsProvider func(ctx MakeVarsContext)
130
131func RegisterMakeVarsProvider(pctx PackageContext, provider MakeVarsProvider) {
132	makeVarsInitProviders = append(makeVarsInitProviders, makeVarsProvider{pctx, provider})
133}
134
135// SingletonMakeVarsProvider is a Singleton with an extra method to provide extra values to be exported to Make.
136type SingletonMakeVarsProvider interface {
137	// MakeVars uses a MakeVarsContext to provide extra values to be exported to Make.
138	MakeVars(ctx MakeVarsContext)
139}
140
141var 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)})
155}
156
157// singletonMakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
158func singletonMakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
159	return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) }
160}
161
162// 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
170///////////////////////////////////////////////////////////////////////////////
171
172func makeVarsSingletonFunc() Singleton {
173	return &makeVarsSingleton{}
174}
175
176type makeVarsSingleton struct{}
177
178type makeVarsProvider struct {
179	pctx PackageContext
180	call MakeVarsProvider
181}
182
183// Collection of makevars providers that are registered in init() methods.
184var makeVarsInitProviders []makeVarsProvider
185
186type makeVarsContext struct {
187	SingletonContext
188	config  Config
189	pctx    PackageContext
190	vars    []makeVarsVariable
191	phonies []phony
192	dists   []dist
193}
194
195var _ MakeVarsContext = &makeVarsContext{}
196
197type makeVarsVariable struct {
198	name   string
199	value  string
200	sort   bool
201	strict bool
202}
203
204type phony struct {
205	name string
206	deps []string
207}
208
209type dist struct {
210	goals []string
211	paths []string
212}
213
214func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) {
215	if !ctx.Config().KatiEnabled() {
216		return
217	}
218
219	outFile := absolutePath(PathForOutput(ctx,
220		"make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
221
222	lateOutFile := absolutePath(PathForOutput(ctx,
223		"late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
224
225	if ctx.Failed() {
226		return
227	}
228
229	var vars []makeVarsVariable
230	var dists []dist
231	var phonies []phony
232
233	providers := append([]makeVarsProvider(nil), makeVarsInitProviders...)
234	providers = append(providers, *ctx.Config().Get(singletonMakeVarsProvidersKey).(*[]makeVarsProvider)...)
235
236	for _, provider := range providers {
237		mctx := &makeVarsContext{
238			SingletonContext: ctx,
239			pctx:             provider.pctx,
240		}
241
242		provider.call(mctx)
243
244		vars = append(vars, mctx.vars...)
245		phonies = append(phonies, mctx.phonies...)
246		dists = append(dists, mctx.dists...)
247	}
248
249	ctx.VisitAllModules(func(m Module) {
250		if provider, ok := m.(ModuleMakeVarsProvider); ok && m.Enabled() {
251			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
263	if ctx.Failed() {
264		return
265	}
266
267	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
288	outBytes := s.writeVars(vars)
289
290	if err := pathtools.WriteFileIfChanged(outFile, outBytes, 0666); err != nil {
291		ctx.Errorf(err.Error())
292	}
293
294	lateOutBytes := s.writeLate(phonies, dists)
295
296	if err := pathtools.WriteFileIfChanged(lateOutFile, lateOutBytes, 0666); err != nil {
297		ctx.Errorf(err.Error())
298	}
299
300}
301
302func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte {
303	buf := &bytes.Buffer{}
304
305	fmt.Fprint(buf, `# Autogenerated file
306
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)),)
316  my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1))))
317  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
329.KATI_READONLY := $(1) SOONG_$(1)
330endef
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
353	fmt.Fprint(buf, `
354ifneq ($(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
378	fmt.Fprintln(buf)
379
380	return buf.Bytes()
381}
382
383func (s *makeVarsSingleton) writeLate(phonies []phony, dists []dist) []byte {
384	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
398	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
405	return buf.Bytes()
406}
407
408func (c *makeVarsContext) DeviceConfig() DeviceConfig {
409	return DeviceConfig{c.Config().deviceConfig}
410}
411
412var ninjaDescaper = strings.NewReplacer("$$", "$")
413
414func (c *makeVarsContext) Eval(ninjaStr string) (string, error) {
415	s, err := c.SingletonContext.Eval(c.pctx, ninjaStr)
416	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
422}
423
424func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) {
425	c.vars = append(c.vars, makeVarsVariable{
426		name:   name,
427		value:  value,
428		strict: strict,
429		sort:   sort,
430	})
431}
432
433func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) {
434	value, err := c.Eval(ninjaStr)
435	if err != nil {
436		c.SingletonContext.Errorf(err.Error())
437	}
438	c.addVariableRaw(name, value, strict, sort)
439}
440
441func (c *makeVarsContext) addPhony(name string, deps []string) {
442	c.phonies = append(c.phonies, phony{name, deps})
443}
444
445func (c *makeVarsContext) addDist(goals []string, paths []string) {
446	c.dists = append(c.dists, dist{
447		goals: goals,
448		paths: paths,
449	})
450}
451
452func (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}
458func (c *makeVarsContext) StrictRaw(name, value string) {
459	c.addVariableRaw(name, value, true, false)
460}
461
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}
468func (c *makeVarsContext) CheckRaw(name, value string) {
469	c.addVariableRaw(name, value, false, false)
470}
471
472func (c *makeVarsContext) Phony(name string, deps ...Path) {
473	c.addPhony(name, Paths(deps).Strings())
474}
475
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}
491