1// Copyright 2018 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 main
16
17import (
18	"bytes"
19	"flag"
20	"fmt"
21	"io/ioutil"
22	"os"
23	"path/filepath"
24	"runtime"
25	"strings"
26
27	"android/soong/android"
28	"android/soong/dexpreopt"
29
30	"github.com/google/blueprint/pathtools"
31)
32
33var (
34	dexpreoptScriptPath   = flag.String("dexpreopt_script", "", "path to output dexpreopt script")
35	globalSoongConfigPath = flag.String("global_soong", "", "path to global configuration file for settings originating from Soong")
36	globalConfigPath      = flag.String("global", "", "path to global configuration file")
37	moduleConfigPath      = flag.String("module", "", "path to module configuration file")
38	outDir                = flag.String("out_dir", "", "path to output directory")
39)
40
41type pathContext struct {
42	config android.Config
43}
44
45func (x *pathContext) Config() android.Config     { return x.config }
46func (x *pathContext) AddNinjaFileDeps(...string) {}
47
48func main() {
49	flag.Parse()
50
51	usage := func(err string) {
52		if err != "" {
53			fmt.Println(err)
54			flag.Usage()
55			os.Exit(1)
56		}
57	}
58
59	if flag.NArg() > 0 {
60		usage("unrecognized argument " + flag.Arg(0))
61	}
62
63	if *dexpreoptScriptPath == "" {
64		usage("path to output dexpreopt script is required")
65	}
66
67	if *globalSoongConfigPath == "" {
68		usage("--global_soong configuration file is required")
69	}
70
71	if *globalConfigPath == "" {
72		usage("--global configuration file is required")
73	}
74
75	if *moduleConfigPath == "" {
76		usage("--module configuration file is required")
77	}
78
79	ctx := &pathContext{android.NullConfig(*outDir)}
80
81	globalSoongConfigData, err := ioutil.ReadFile(*globalSoongConfigPath)
82	if err != nil {
83		fmt.Fprintf(os.Stderr, "error reading global Soong config %q: %s\n", *globalSoongConfigPath, err)
84		os.Exit(2)
85	}
86
87	globalSoongConfig, err := dexpreopt.ParseGlobalSoongConfig(ctx, globalSoongConfigData)
88	if err != nil {
89		fmt.Fprintf(os.Stderr, "error parsing global Soong config %q: %s\n", *globalSoongConfigPath, err)
90		os.Exit(2)
91	}
92
93	globalConfigData, err := ioutil.ReadFile(*globalConfigPath)
94	if err != nil {
95		fmt.Fprintf(os.Stderr, "error reading global config %q: %s\n", *globalConfigPath, err)
96		os.Exit(2)
97	}
98
99	globalConfig, err := dexpreopt.ParseGlobalConfig(ctx, globalConfigData)
100	if err != nil {
101		fmt.Fprintf(os.Stderr, "error parsing global config %q: %s\n", *globalConfigPath, err)
102		os.Exit(2)
103	}
104
105	moduleConfigData, err := ioutil.ReadFile(*moduleConfigPath)
106	if err != nil {
107		fmt.Fprintf(os.Stderr, "error reading module config %q: %s\n", *moduleConfigPath, err)
108		os.Exit(2)
109	}
110
111	moduleConfig, err := dexpreopt.ParseModuleConfig(ctx, moduleConfigData)
112	if err != nil {
113		fmt.Fprintf(os.Stderr, "error parsing module config %q: %s\n", *moduleConfigPath, err)
114		os.Exit(2)
115	}
116
117	moduleConfig.DexPath = android.PathForTesting("$1")
118
119	defer func() {
120		if r := recover(); r != nil {
121			switch x := r.(type) {
122			case runtime.Error:
123				panic(x)
124			case error:
125				fmt.Fprintln(os.Stderr, "error:", r)
126				os.Exit(3)
127			default:
128				panic(x)
129			}
130		}
131	}()
132
133	writeScripts(ctx, globalSoongConfig, globalConfig, moduleConfig, *dexpreoptScriptPath)
134}
135
136func writeScripts(ctx android.PathContext, globalSoong *dexpreopt.GlobalSoongConfig,
137	global *dexpreopt.GlobalConfig, module *dexpreopt.ModuleConfig, dexpreoptScriptPath string) {
138	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, globalSoong, global, module)
139	if err != nil {
140		panic(err)
141	}
142
143	installDir := module.BuildPath.InSameDir(ctx, "dexpreopt_install")
144
145	dexpreoptRule.Command().FlagWithArg("rm -rf ", installDir.String())
146	dexpreoptRule.Command().FlagWithArg("mkdir -p ", installDir.String())
147
148	for _, install := range dexpreoptRule.Installs() {
149		installPath := installDir.Join(ctx, strings.TrimPrefix(install.To, "/"))
150		dexpreoptRule.Command().Text("mkdir -p").Flag(filepath.Dir(installPath.String()))
151		dexpreoptRule.Command().Text("cp -f").Input(install.From).Output(installPath)
152	}
153	dexpreoptRule.Command().Tool(globalSoong.SoongZip).
154		FlagWithArg("-o ", "$2").
155		FlagWithArg("-C ", installDir.String()).
156		FlagWithArg("-D ", installDir.String())
157
158	write := func(rule *android.RuleBuilder, file string) {
159		script := &bytes.Buffer{}
160		script.WriteString(scriptHeader)
161		for _, c := range rule.Commands() {
162			script.WriteString(c)
163			script.WriteString("\n\n")
164		}
165
166		depFile := &bytes.Buffer{}
167
168		fmt.Fprint(depFile, `: \`+"\n")
169		for _, tool := range rule.Tools() {
170			fmt.Fprintf(depFile, `    %s \`+"\n", tool)
171		}
172		for _, input := range rule.Inputs() {
173			// Assume the rule that ran the script already has a dependency on the input file passed on the
174			// command line.
175			if input.String() != "$1" {
176				fmt.Fprintf(depFile, `    %s \`+"\n", input)
177			}
178		}
179		depFile.WriteString("\n")
180
181		fmt.Fprintln(script, "rm -f $2.d")
182		// Write the output path unescaped so the $2 gets expanded
183		fmt.Fprintln(script, `echo -n $2 > $2.d`)
184		// Write the rest of the depsfile using cat <<'EOF', which will not do any shell expansion on
185		// the contents to preserve backslashes and special characters in filenames.
186		fmt.Fprintf(script, "cat >> $2.d <<'EOF'\n%sEOF\n", depFile.String())
187
188		err := pathtools.WriteFileIfChanged(file, script.Bytes(), 0755)
189		if err != nil {
190			panic(err)
191		}
192	}
193
194	// The written scripts will assume the input is $1 and the output is $2
195	if module.DexPath.String() != "$1" {
196		panic(fmt.Errorf("module.DexPath must be '$1', was %q", module.DexPath))
197	}
198
199	write(dexpreoptRule, dexpreoptScriptPath)
200}
201
202const scriptHeader = `#!/bin/bash
203
204err() {
205  errno=$?
206  echo "error: $0:$1 exited with status $errno" >&2
207  echo "error in command:" >&2
208  sed -n -e "$1p" $0 >&2
209  if [ "$errno" -ne 0 ]; then
210    exit $errno
211  else
212    exit 1
213  fi
214}
215
216trap 'err $LINENO' ERR
217
218`
219