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	"io/ioutil"
21	"os"
22
23	"github.com/google/blueprint"
24	"github.com/google/blueprint/proptools"
25)
26
27///////////////////////////////////////////////////////////////////////////////
28// Interface for other packages to use to declare make variables
29type MakeVarsContext interface {
30	Config() Config
31
32	// Verify the make variable matches the Soong version, fail the build
33	// if it does not. If the make variable is empty, just set it.
34	Strict(name, ninjaStr string)
35	// Check to see if the make variable matches the Soong version, warn if
36	// it does not. If the make variable is empty, just set it.
37	Check(name, ninjaStr string)
38
39	// These are equivalent to the above, but sort the make and soong
40	// variables before comparing them. They also show the unique entries
41	// in each list when displaying the difference, instead of the entire
42	// string.
43	StrictSorted(name, ninjaStr string)
44	CheckSorted(name, ninjaStr string)
45
46	// Evaluates a ninja string and returns the result. Used if more
47	// complicated modification needs to happen before giving it to Make.
48	Eval(ninjaStr string) (string, error)
49
50	// These are equivalent to Strict and Check, but do not attempt to
51	// evaluate the values before writing them to the Makefile. They can
52	// be used when all ninja variables have already been evaluated through
53	// Eval().
54	StrictRaw(name, value string)
55	CheckRaw(name, value string)
56}
57
58type MakeVarsProvider func(ctx MakeVarsContext)
59
60func RegisterMakeVarsProvider(pctx blueprint.PackageContext, provider MakeVarsProvider) {
61	makeVarsProviders = append(makeVarsProviders, makeVarsProvider{pctx, provider})
62}
63
64///////////////////////////////////////////////////////////////////////////////
65
66func init() {
67	RegisterSingletonType("makevars", makeVarsSingletonFunc)
68}
69
70func makeVarsSingletonFunc() blueprint.Singleton {
71	return &makeVarsSingleton{}
72}
73
74type makeVarsSingleton struct{}
75
76type makeVarsProvider struct {
77	pctx blueprint.PackageContext
78	call MakeVarsProvider
79}
80
81var makeVarsProviders []makeVarsProvider
82
83type makeVarsContext struct {
84	config Config
85	ctx    blueprint.SingletonContext
86	pctx   blueprint.PackageContext
87	vars   []makeVarsVariable
88}
89
90var _ MakeVarsContext = &makeVarsContext{}
91
92type makeVarsVariable struct {
93	name   string
94	value  string
95	sort   bool
96	strict bool
97}
98
99func (s *makeVarsSingleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
100	config := ctx.Config().(Config)
101
102	if !config.EmbeddedInMake() {
103		return
104	}
105
106	outFile := PathForOutput(ctx, "make_vars"+proptools.String(config.ProductVariables.Make_suffix)+".mk").String()
107
108	if ctx.Failed() {
109		return
110	}
111
112	vars := []makeVarsVariable{}
113	for _, provider := range makeVarsProviders {
114		mctx := &makeVarsContext{
115			config: config,
116			ctx:    ctx,
117			pctx:   provider.pctx,
118		}
119
120		provider.call(mctx)
121
122		vars = append(vars, mctx.vars...)
123	}
124
125	if ctx.Failed() {
126		return
127	}
128
129	outBytes := s.writeVars(vars)
130
131	if _, err := os.Stat(outFile); err == nil {
132		if data, err := ioutil.ReadFile(outFile); err == nil {
133			if bytes.Equal(data, outBytes) {
134				return
135			}
136		}
137	}
138
139	if err := ioutil.WriteFile(outFile, outBytes, 0666); err != nil {
140		ctx.Errorf(err.Error())
141	}
142}
143
144func (s *makeVarsSingleton) writeVars(vars []makeVarsVariable) []byte {
145	buf := &bytes.Buffer{}
146
147	fmt.Fprintln(buf, `# Autogenerated file
148
149# Compares SOONG_$(1) against $(1), and warns if they are not equal.
150#
151# If the original variable is empty, then just set it to the SOONG_ version.
152#
153# $(1): Name of the variable to check
154# $(2): If not-empty, sort the values before comparing
155# $(3): Extra snippet to run if it does not match
156define soong-compare-var
157ifneq ($$($(1)),)
158  my_val_make := $$(strip $(if $(2),$$(sort $$($(1))),$$($(1))))
159  my_val_soong := $(if $(2),$$(sort $$(SOONG_$(1))),$$(SOONG_$(1)))
160  ifneq ($$(my_val_make),$$(my_val_soong))
161    $$(warning $(1) does not match between Make and Soong:)
162    $(if $(2),$$(warning Make  adds: $$(filter-out $$(my_val_soong),$$(my_val_make))),$$(warning Make : $$(my_val_make)))
163    $(if $(2),$$(warning Soong adds: $$(filter-out $$(my_val_make),$$(my_val_soong))),$$(warning Soong: $$(my_val_soong)))
164    $(3)
165  endif
166  my_val_make :=
167  my_val_soong :=
168else
169  $(1) := $$(SOONG_$(1))
170endif
171.KATI_READONLY := $(1) SOONG_$(1)
172endef
173
174my_check_failed := false
175
176`)
177
178	// Write all the strict checks out first so that if one of them errors,
179	// we get all of the strict errors printed, but not the non-strict
180	// warnings.
181	for _, v := range vars {
182		if !v.strict {
183			continue
184		}
185
186		sort := ""
187		if v.sort {
188			sort = "true"
189		}
190
191		fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
192		fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s,my_check_failed := true))\n\n", v.name, sort)
193	}
194
195	fmt.Fprintln(buf, `
196ifneq ($(my_check_failed),false)
197  $(error Soong variable check failed)
198endif
199my_check_failed :=
200
201
202`)
203
204	for _, v := range vars {
205		if v.strict {
206			continue
207		}
208
209		sort := ""
210		if v.sort {
211			sort = "true"
212		}
213
214		fmt.Fprintf(buf, "SOONG_%s := %s\n", v.name, v.value)
215		fmt.Fprintf(buf, "$(eval $(call soong-compare-var,%s,%s))\n\n", v.name, sort)
216	}
217
218	fmt.Fprintln(buf, "\nsoong-compare-var :=")
219
220	return buf.Bytes()
221}
222
223func (c *makeVarsContext) Config() Config {
224	return c.config
225}
226
227func (c *makeVarsContext) Eval(ninjaStr string) (string, error) {
228	return c.ctx.Eval(c.pctx, ninjaStr)
229}
230
231func (c *makeVarsContext) addVariableRaw(name, value string, strict, sort bool) {
232	c.vars = append(c.vars, makeVarsVariable{
233		name:   name,
234		value:  value,
235		strict: strict,
236		sort:   sort,
237	})
238}
239
240func (c *makeVarsContext) addVariable(name, ninjaStr string, strict, sort bool) {
241	value, err := c.Eval(ninjaStr)
242	if err != nil {
243		c.ctx.Errorf(err.Error())
244	}
245	c.addVariableRaw(name, value, strict, sort)
246}
247
248func (c *makeVarsContext) Strict(name, ninjaStr string) {
249	c.addVariable(name, ninjaStr, true, false)
250}
251func (c *makeVarsContext) StrictSorted(name, ninjaStr string) {
252	c.addVariable(name, ninjaStr, true, true)
253}
254func (c *makeVarsContext) StrictRaw(name, value string) {
255	c.addVariableRaw(name, value, true, false)
256}
257
258func (c *makeVarsContext) Check(name, ninjaStr string) {
259	c.addVariable(name, ninjaStr, false, false)
260}
261func (c *makeVarsContext) CheckSorted(name, ninjaStr string) {
262	c.addVariable(name, ninjaStr, false, true)
263}
264func (c *makeVarsContext) CheckRaw(name, value string) {
265	c.addVariableRaw(name, value, false, false)
266}
267