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	"strconv"
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", strconv.Itoa(ctx.Config().MinSupportedSdkVersion()))
34}
35
36///////////////////////////////////////////////////////////////////////////////
37// Interface for other packages to use to declare make variables
38type MakeVarsContext interface {
39	Config() Config
40	DeviceConfig() DeviceConfig
41	AddNinjaFileDeps(deps ...string)
42
43	ModuleName(module blueprint.Module) string
44	ModuleDir(module blueprint.Module) string
45	ModuleSubDir(module blueprint.Module) string
46	ModuleType(module blueprint.Module) string
47	BlueprintFile(module blueprint.Module) string
48
49	ModuleErrorf(module blueprint.Module, format string, args ...interface{})
50	Errorf(format string, args ...interface{})
51	Failed() bool
52
53	VisitAllModules(visit func(Module))
54	VisitAllModulesIf(pred func(Module) bool, visit func(Module))
55
56	// Verify the make variable matches the Soong version, fail the build
57	// if it does not. If the make variable is empty, just set it.
58	Strict(name, ninjaStr string)
59	// Check to see if the make variable matches the Soong version, warn if
60	// it does not. If the make variable is empty, just set it.
61	Check(name, ninjaStr string)
62
63	// These are equivalent to the above, but sort the make and soong
64	// variables before comparing them. They also show the unique entries
65	// in each list when displaying the difference, instead of the entire
66	// string.
67	StrictSorted(name, ninjaStr string)
68	CheckSorted(name, ninjaStr string)
69
70	// Evaluates a ninja string and returns the result. Used if more
71	// complicated modification needs to happen before giving it to Make.
72	Eval(ninjaStr string) (string, error)
73
74	// These are equivalent to Strict and Check, but do not attempt to
75	// evaluate the values before writing them to the Makefile. They can
76	// be used when all ninja variables have already been evaluated through
77	// Eval().
78	StrictRaw(name, value string)
79	CheckRaw(name, value string)
80
81	// GlobWithDeps returns a list of files that match the specified pattern but do not match any
82	// of the patterns in excludes.  It also adds efficient dependencies to rerun the primary
83	// builder whenever a file matching the pattern as added or removed, without rerunning if a
84	// file that does not match the pattern is added to a searched directory.
85	GlobWithDeps(pattern string, excludes []string) ([]string, error)
86
87	// Phony creates a phony rule in Make, which will allow additional DistForGoal
88	// dependencies to be added to it.  Phony can be called on the same name multiple
89	// times to add additional dependencies.
90	Phony(names string, deps ...Path)
91
92	// DistForGoal creates a rule to copy one or more Paths to the artifacts
93	// directory on the build server when the specified goal is built.
94	DistForGoal(goal string, paths ...Path)
95
96	// DistForGoalWithFilename creates a rule to copy a Path to the artifacts
97	// directory on the build server with the given filename when the specified
98	// goal is built.
99	DistForGoalWithFilename(goal string, path Path, filename string)
100
101	// DistForGoals creates a rule to copy one or more Paths to the artifacts
102	// directory on the build server when any of the specified goals are built.
103	DistForGoals(goals []string, paths ...Path)
104
105	// DistForGoalsWithFilename creates a rule to copy a Path to the artifacts
106	// directory on the build server with the given filename when any of the
107	// specified goals are built.
108	DistForGoalsWithFilename(goals []string, path Path, filename string)
109}
110
111var _ PathContext = MakeVarsContext(nil)
112
113type MakeVarsProvider func(ctx MakeVarsContext)
114
115func RegisterMakeVarsProvider(pctx PackageContext, provider MakeVarsProvider) {
116	makeVarsProviders = append(makeVarsProviders, makeVarsProvider{pctx, provider})
117}
118
119// SingletonMakeVarsProvider is a Singleton with an extra method to provide extra values to be exported to Make.
120type SingletonMakeVarsProvider interface {
121	Singleton
122
123	// MakeVars uses a MakeVarsContext to provide extra values to be exported to Make.
124	MakeVars(ctx MakeVarsContext)
125}
126
127// registerSingletonMakeVarsProvider adds a singleton that implements SingletonMakeVarsProvider to the list of
128// MakeVarsProviders to run.
129func registerSingletonMakeVarsProvider(singleton SingletonMakeVarsProvider) {
130	makeVarsProviders = append(makeVarsProviders, makeVarsProvider{pctx, SingletonmakeVarsProviderAdapter(singleton)})
131}
132
133// SingletonmakeVarsProviderAdapter converts a SingletonMakeVarsProvider to a MakeVarsProvider.
134func SingletonmakeVarsProviderAdapter(singleton SingletonMakeVarsProvider) MakeVarsProvider {
135	return func(ctx MakeVarsContext) { singleton.MakeVars(ctx) }
136}
137
138///////////////////////////////////////////////////////////////////////////////
139
140func makeVarsSingletonFunc() Singleton {
141	return &makeVarsSingleton{}
142}
143
144type makeVarsSingleton struct{}
145
146type makeVarsProvider struct {
147	pctx PackageContext
148	call MakeVarsProvider
149}
150
151var makeVarsProviders []makeVarsProvider
152
153type makeVarsContext struct {
154	SingletonContext
155	config  Config
156	pctx    PackageContext
157	vars    []makeVarsVariable
158	phonies []phony
159	dists   []dist
160}
161
162var _ MakeVarsContext = &makeVarsContext{}
163
164type makeVarsVariable struct {
165	name   string
166	value  string
167	sort   bool
168	strict bool
169}
170
171type phony struct {
172	name string
173	deps []string
174}
175
176type dist struct {
177	goals []string
178	paths []string
179}
180
181func (s *makeVarsSingleton) GenerateBuildActions(ctx SingletonContext) {
182	if !ctx.Config().EmbeddedInMake() {
183		return
184	}
185
186	outFile := absolutePath(PathForOutput(ctx,
187		"make_vars"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
188
189	lateOutFile := absolutePath(PathForOutput(ctx,
190		"late"+proptools.String(ctx.Config().productVariables.Make_suffix)+".mk").String())
191
192	if ctx.Failed() {
193		return
194	}
195
196	var vars []makeVarsVariable
197	var dists []dist
198	var phonies []phony
199	for _, provider := range makeVarsProviders {
200		mctx := &makeVarsContext{
201			SingletonContext: ctx,
202			pctx:             provider.pctx,
203		}
204
205		provider.call(mctx)
206
207		vars = append(vars, mctx.vars...)
208		phonies = append(phonies, mctx.phonies...)
209		dists = append(dists, mctx.dists...)
210	}
211
212	if ctx.Failed() {
213		return
214	}
215
216	outBytes := s.writeVars(vars)
217
218	if err := pathtools.WriteFileIfChanged(outFile, outBytes, 0666); err != nil {
219		ctx.Errorf(err.Error())
220	}
221
222	lateOutBytes := s.writeLate(phonies, dists)
223
224	if err := pathtools.WriteFileIfChanged(lateOutFile, lateOutBytes, 0666); err != nil {
225		ctx.Errorf(err.Error())
226	}
227
228}
229
230func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte {
231	buf := &bytes.Buffer{}
232
233	fmt.Fprint(buf, `# Autogenerated file
234
235# Compares SOONG_$(1) against $(1), and warns if they are not equal.
236#
237# If the original variable is empty, then just set it to the SOONG_ version.
238#
239# $(1): Name of the variable to check
240# $(2): If not-empty, sort the values before comparing
241# $(3): Extra snippet to run if it does not match
242define soong-compare-var
243ifneq ($$($(1)),)
244  my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1))))
245  my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1)))
246  ifneq ($$(my_val_make),$$(my_val_soong))
247    $$(warning $(1) does not match between Make and Soong:)
248    $(if $(2),$$(warning Make  adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make)))
249    $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong)))
250    $(3)
251  endif
252  my_val_make :=
253  my_val_soong :=
254else
255  $(1) := $$(SOONG_$(1))
256endif
257.KATI_READONLY := $(1) SOONG_$(1)
258endef
259
260my_check_failed := false
261
262`)
263
264	// Write all the strict checks out first so that if one of them errors,
265	// we get all of the strict errors printed, but not the non-strict
266	// warnings.
267	for _, v := range vars {
268		if !v.strict {
269			continue
270		}
271
272		sort := ""
273		if v.sort {
274			sort = "true"
275		}
276
277		fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
278		fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort)
279	}
280
281	fmt.Fprint(buf, `
282ifneq ($(my_check_failed),false)
283  $(error Soong variable check failed)
284endif
285my_check_failed :=
286
287
288`)
289
290	for _, v := range vars {
291		if v.strict {
292			continue
293		}
294
295		sort := ""
296		if v.sort {
297			sort = "true"
298		}
299
300		fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
301		fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort)
302	}
303
304	fmt.Fprintln(buf, "\nsoong-compare-var :=")
305
306	fmt.Fprintln(buf)
307
308	return buf.Bytes()
309}
310
311func (s *makeVarsSingleton) writeLate(phonies []phony, dists []dist) []byte {
312	buf := &bytes.Buffer{}
313
314	fmt.Fprint(buf, `# Autogenerated file
315
316# Values written by Soong read after parsing all Android.mk files.
317
318
319`)
320
321	for _, phony := range phonies {
322		fmt.Fprintf(buf, ".PHONY: %s\n", phony.name)
323		fmt.Fprintf(buf, "%s: %s\n", phony.name, strings.Join(phony.deps, "\\\n  "))
324	}
325
326	fmt.Fprintln(buf)
327
328	for _, dist := range dists {
329		fmt.Fprintf(buf, "$(call dist-for-goals,%s,%s)\n",
330			strings.Join(dist.goals, " "), strings.Join(dist.paths, " "))
331	}
332
333	return buf.Bytes()
334}
335
336func (c *makeVarsContext) DeviceConfig() DeviceConfig {
337	return DeviceConfig{c.Config().deviceConfig}
338}
339
340var ninjaDescaper = strings.NewReplacer("$$", "$")
341
342func (c *makeVarsContext) Eval(ninjaStr string) (string, error) {
343	s, err := c.SingletonContext.Eval(c.pctx, ninjaStr)
344	if err != nil {
345		return "", err
346	}
347	// SingletonContext.Eval returns an exapnded string that is valid for a ninja file, de-escape $$ to $ for use
348	// in a Makefile
349	return ninjaDescaper.Replace(s), nil
350}
351
352func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) {
353	c.vars = append(c.vars, makeVarsVariable{
354		name:   name,
355		value:  value,
356		strict: strict,
357		sort:   sort,
358	})
359}
360
361func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) {
362	value, err := c.Eval(ninjaStr)
363	if err != nil {
364		c.SingletonContext.Errorf(err.Error())
365	}
366	c.addVariableRaw(name, value, strict, sort)
367}
368
369func (c *makeVarsContext) addPhony(name string, deps []string) {
370	c.phonies = append(c.phonies, phony{name, deps})
371}
372
373func (c *makeVarsContext) addDist(goals []string, paths []string) {
374	c.dists = append(c.dists, dist{
375		goals: goals,
376		paths: paths,
377	})
378}
379
380func (c *makeVarsContext) Strict(name, ninjaStr string) {
381	c.addVariable(name, ninjaStr, true, false)
382}
383func (c *makeVarsContext) StrictSorted(name, ninjaStr string) {
384	c.addVariable(name, ninjaStr, true, true)
385}
386func (c *makeVarsContext) StrictRaw(name, value string) {
387	c.addVariableRaw(name, value, true, false)
388}
389
390func (c *makeVarsContext) Check(name, ninjaStr string) {
391	c.addVariable(name, ninjaStr, false, false)
392}
393func (c *makeVarsContext) CheckSorted(name, ninjaStr string) {
394	c.addVariable(name, ninjaStr, false, true)
395}
396func (c *makeVarsContext) CheckRaw(name, value string) {
397	c.addVariableRaw(name, value, false, false)
398}
399
400func (c *makeVarsContext) Phony(name string, deps ...Path) {
401	c.addPhony(name, Paths(deps).Strings())
402}
403
404func (c *makeVarsContext) DistForGoal(goal string, paths ...Path) {
405	c.DistForGoals([]string{goal}, paths...)
406}
407
408func (c *makeVarsContext) DistForGoalWithFilename(goal string, path Path, filename string) {
409	c.DistForGoalsWithFilename([]string{goal}, path, filename)
410}
411
412func (c *makeVarsContext) DistForGoals(goals []string, paths ...Path) {
413	c.addDist(goals, Paths(paths).Strings())
414}
415
416func (c *makeVarsContext) DistForGoalsWithFilename(goals []string, path Path, filename string) {
417	c.addDist(goals, []string{path.String() + ":" + filename})
418}
419