xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/dex/dex.go (revision 9e965d6fece27a77de5377433c2f7e6999b8cc0b)
1// Copyright 2018 The Bazel Authors. 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
15// Package dex provides a thin wrapper around d8 to handle corner cases
16package dex
17
18import (
19	"archive/zip"
20	"bufio"
21	"flag"
22	"fmt"
23	"io/ioutil"
24	"log"
25	"os"
26	"os/exec"
27	"path/filepath"
28	"strings"
29	"sync"
30
31	"src/common/golang/flags"
32	"src/common/golang/shard"
33	"src/common/golang/ziputils"
34	"src/tools/ak/types"
35)
36
37var (
38	// Cmd defines the command to run
39	Cmd = types.Command{
40		Init: Init,
41		Run:  Run,
42		Desc: desc,
43		Flags: []string{
44			"desugar",
45			"android_jar",
46			"desugar_core_libs",
47			"classpath",
48			"d8",
49			"intermediate",
50			"in",
51			"out",
52		},
53	}
54
55	tmp struct {
56		Dir string
57	}
58
59	// Flag variables
60	desugar, androidJar, d8, in   string
61	classpaths, outs, outputDir   flags.StringList
62	desugarCoreLibs, intermediate bool
63
64	initOnce sync.Once
65)
66
67// Init initializes manifest flags
68func Init() {
69	initOnce.Do(func() {
70		flag.StringVar(&desugar, "desugar", "", "Path to desugar tool")
71		flag.StringVar(&androidJar, "android_jar", "", "Required for desugar, path to android.jar")
72		flag.Var(&classpaths, "classpath", "(Optional) Path to library resource(s) for desugar")
73		flag.BoolVar(&desugarCoreLibs, "desugar_core_libs", false, "Desugar Java 8 core libs, default false")
74		flag.StringVar(&d8, "d8", "", "Path to d8 dexer")
75		flag.BoolVar(&intermediate, "intermediate", false, "Compile for later merging, default false")
76		flag.StringVar(&in, "in", "", "Path to input")
77		flag.Var(&outs, "out", "Path to output, if more than one specified, output is sharded across files.")
78	})
79}
80
81func desc() string {
82	return "Dex converts Java byte code to Dex code."
83}
84
85// Run is the main entry point
86func Run() {
87	if desugar != "" && androidJar == "" {
88		log.Fatal("--android_jar is required for desugaring")
89	}
90	if d8 == "" || in == "" || outs == nil {
91		log.Fatal("Missing required flags. Must specify --d8 --in --out")
92	}
93	sc := len(outs)
94	if sc > 256 {
95		log.Fatalf("%d: is an unreasonable shard count (want [1 to 256])", sc)
96	}
97
98	var err error
99	tmp.Dir, err = ioutil.TempDir("", "dex")
100	if err != nil {
101		log.Fatalf("Error creating temp dir: %v", err)
102	}
103	defer os.RemoveAll(tmp.Dir)
104
105	notEmpty, err := hasCode(in)
106	if err != nil {
107		log.Fatal(err)
108	}
109
110	if notEmpty {
111		jar := in
112		if desugar != "" {
113			jar = filepath.Join(tmp.Dir, "desugared.jar")
114			if err = desugarJar(in, jar); err != nil {
115				log.Fatalf("Error desugaring %v: %v", in, err)
116			}
117		}
118		if sc == 1 {
119			if err = dex(jar, outs[0]); err != nil {
120				log.Fatalf("Dex error: %v", err)
121			}
122		} else {
123			out := filepath.Join(tmp.Dir, "dexed.zip")
124			if err = dex(jar, out); err != nil {
125				log.Fatalf("Dex error: %v", err)
126			}
127			if err = zipShard(out, outs); err != nil {
128				log.Fatalf("ZipShard error: %v", err)
129			}
130		}
131	} else {
132		for _, out := range outs {
133			if err := ziputils.EmptyZip(out); err != nil {
134				log.Fatalf("Error creating empty zip archive: %v", err)
135			}
136		}
137	}
138}
139
140func createFlagFile(args []string) (string, error) {
141	f, err := ioutil.TempFile(tmp.Dir, "flags")
142	if err != nil {
143		return "", err
144	}
145	for _, arg := range args {
146		if _, err := f.WriteString(arg + "\n"); err != nil {
147			return "", err
148		}
149	}
150	if err := f.Close(); err != nil {
151		return "", err
152	}
153	return f.Name(), nil
154}
155
156func hasCode(f string) (bool, error) {
157	reader, err := zip.OpenReader(f)
158	if err != nil {
159		return false, fmt.Errorf("Opening zip %q failed: %v", f, err)
160	}
161	defer reader.Close()
162
163	for _, file := range reader.File {
164		ext := filepath.Ext(file.Name)
165		if ext == ".class" || ext == ".dex" {
166			return true, nil
167		}
168	}
169	return false, nil
170}
171
172func desugarJar(in, out string) error {
173	args := []string{
174		"--input",
175		in,
176		"--bootclasspath_entry",
177		androidJar,
178		"--output",
179		out,
180	}
181	if desugarCoreLibs {
182		args = append(args, "--desugar_supported_core_libs")
183	}
184	for _, cp := range classpaths {
185		args = append(args, "--classpath_entry", cp)
186	}
187	return runCmd(desugar, args)
188}
189
190func dex(in, out string) error {
191	args := []string{
192		"--min-api",
193		"21",
194		"--no-desugaring",
195		"--output",
196		out,
197	}
198	if intermediate {
199		args = append(args, "--file-per-class")
200		args = append(args, "--intermediate")
201	}
202	args = append(args, in)
203	return runCmd(d8, args)
204}
205
206func runCmd(cmd string, args []string) error {
207	flagFile, err := createFlagFile(args)
208	if err != nil {
209		return fmt.Errorf("Error creating flag file: %v", err)
210	}
211	output, err := exec.Command(cmd, "@"+flagFile).CombinedOutput()
212	if err != nil {
213		return fmt.Errorf("%v:\n%s", err, output)
214	}
215	return nil
216}
217
218func zipShard(input string, outs []string) error {
219	zr, err := zip.OpenReader(input)
220	if err != nil {
221		return fmt.Errorf("%s: cannot open for input: %v", input, err)
222	}
223	defer zr.Close()
224
225	if len(outs) < 2 {
226		log.Fatalf("Need at least two output shards)")
227	}
228
229	zws := make([]*zip.Writer, len(outs))
230	for i, out := range outs {
231		outDir := filepath.Dir(out)
232		if _, err := os.Stat(outDir); os.IsNotExist(err) {
233			if err := os.MkdirAll(outDir, 0755); err != nil {
234				return fmt.Errorf("%s: could not make dir: %v", input, outDir)
235			}
236		}
237		outF, err := os.Create(out)
238		if err != nil {
239			return fmt.Errorf("%s: could not create output file: %s %v", out, outDir, err)
240		}
241		w := bufio.NewWriterSize(outF, 2<<16)
242		zw := zip.NewWriter(w)
243		defer func() error {
244			if err := zw.Close(); err != nil {
245				return fmt.Errorf("%s: closing zip failed: %v", out, err)
246			}
247			if err := w.Flush(); err != nil {
248				return fmt.Errorf("%s: flushing output file failed: %v", out, err)
249			}
250			if err := outF.Close(); err != nil {
251				return fmt.Errorf("%s: closing output file failed: %v", out, err)
252			}
253			return nil
254		}()
255		zws[i] = zw
256	}
257
258	err = shard.ZipShard(&zr.Reader, zws, shardFn)
259	if err != nil {
260		return fmt.Errorf("%s: sharder failed: %v", input, err)
261	}
262	return nil
263}
264
265func shardFn(name string, shardCount int) int {
266	// Sharding function which ensures that a class and all its inner classes are
267	// placed in the same shard. An important side effect of this is that all D8
268	// synthetics are in the same shard as their context, as a synthetic is named
269	// <context>$$ExternalSyntheticXXXN.
270	index := len(name)
271	if strings.HasSuffix(name, ".dex") {
272		index -= 4
273	} else {
274		log.Fatalf("Name expected to end with '.dex', was: %s", name)
275	}
276	trimIndex := strings.IndexAny(name, "$-")
277	if trimIndex > -1 {
278		index = trimIndex
279	}
280	return shard.FNV(name[:index], shardCount)
281}
282