xref: /aosp_15_r20/external/bazelbuild-rules_android/src/tools/ak/shellapk/shellapk.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 shellapk creates a minimal shell apk.
16package shellapk
17
18import (
19	"archive/zip"
20	"flag"
21	"fmt"
22	"io"
23	"io/ioutil"
24	"log"
25	"os"
26	"path/filepath"
27	"strings"
28	"sync"
29
30	"src/common/golang/fileutils"
31	"src/common/golang/flags"
32	"src/common/golang/ziputils"
33	"src/tools/ak/types"
34)
35
36const (
37	swigdepsFileName    = "nativedeps.txt"
38	jarManifestFileName = "META-INF/MANIFEST.MF"
39
40	// see https://docs.oracle.com/javase/tutorial/deployment/jar/defman.html
41	defJarManifestContents = "Manifest-Version: 1.0\nCreated-By: ak"
42)
43
44var (
45	// Cmd defines the command to run shellapk.
46	Cmd = types.Command{
47		Init:  Init,
48		Run:   Run,
49		Desc:  desc,
50		Flags: []string{"java_resources", "dummy_native_libs", "jdk", "dex_file", "manifest_package_name", "in_application_class_name", "shell_app", "android_manifest", "arsc_path", "android_resources_zip", "linked_native_library"},
51	}
52
53	// Variables to hold flag values.
54	javaRes         flags.StringList
55	dummyNativeLibs string
56	jdk             string
57	dexFile         string
58	manifestPkgName string
59	inAppClassName  string
60	out             string
61	manifest        string
62	arsc            string
63	resZip          string
64	linkedNativeLib string
65
66	initOnce sync.Once
67)
68
69// Init initializes shellapk.
70func Init() {
71	initOnce.Do(func() {
72		flag.Var(&javaRes, "java_resources", "Path to java resource file.")
73		flag.StringVar(&dummyNativeLibs, "dummy_native_libs", "", "Native libraries files.")
74		flag.StringVar(&jdk, "jdk", "", "JDK path.")
75		flag.StringVar(&dexFile, "dex_file", "", "Dex file with stubby classes for apk.")
76		flag.StringVar(&manifestPkgName, "manifest_package_name", "", "Manifest package name.")
77		flag.StringVar(&inAppClassName, "in_application_class_name", "", "Path for the application class name.")
78		flag.StringVar(&out, "shell_app", "", "Output path for the shell apk.")
79		flag.StringVar(&manifest, "android_manifest", "", "Binary AndroidManifest.xml artifact.")
80		flag.StringVar(&arsc, "arsc_path", "", "Path to the arsc table.")
81		flag.StringVar(&resZip, "android_resources_zip", "", "Android resources files.")
82		flag.StringVar(&linkedNativeLib, "linked_native_library", "", "Linked native lib name that needs to be outputed on the swigdeps file.")
83	})
84}
85
86func desc() string {
87	return "Shellapk creates a minimal shell apk."
88}
89
90// Run is the entry point for shellapk.
91func Run() {
92	if err := doWork(
93		out,
94		javaRes,
95		dummyNativeLibs,
96		jdk,
97		dexFile,
98		manifestPkgName,
99		inAppClassName,
100		manifest,
101		arsc,
102		resZip,
103		linkedNativeLib); err != nil {
104		log.Fatalf("Error creating shellapk: %v", err)
105	}
106}
107
108func doWork(out string, javaRes []string, dummyNativeLibs, jdk, dexFile, manifestPkgName, inAppClassName, manifest, arsc, resZip, linkedNativeLib string) error {
109	// Create temp dir for all apk files
110	apkDir, err := ioutil.TempDir("", "shellapk")
111	if err != nil {
112		return err
113	}
114	defer os.RemoveAll(apkDir)
115
116	// Exctract dexes
117	if err := extractDexes(apkDir, dexFile); err != nil {
118		return err
119	}
120
121	// Extract java resources
122	for _, f := range javaRes {
123		if err := ziputils.Unzip(f, apkDir); err != nil {
124			return err
125		}
126	}
127
128	// Copy manifest
129	if err := fileutils.Copy(manifest, filepath.Join(apkDir, "AndroidManifest.xml")); err != nil {
130		return err
131	}
132
133	// Write output zip
134	w, err := os.Create(out)
135	if err != nil {
136		return err
137	}
138	defer w.Close()
139	zipOut := zip.NewWriter(w)
140	defer zipOut.Close()
141
142	// Write all files in apkDir
143	err = filepath.Walk(apkDir, func(path string, f os.FileInfo, err error) error {
144		if err != nil {
145			return err
146		}
147
148		if !f.IsDir() {
149			// Filter out META-INF/MANIFEST.MF. If this file is not correct
150			// the apk signer will refuse to touch the apk.
151			if path[len(apkDir)+1:] == jarManifestFileName {
152				return nil
153			}
154
155			n := strings.TrimLeft(strings.TrimPrefix(path, apkDir), "/")
156			if err := ziputils.WriteFile(zipOut, path, n); err != nil {
157				return err
158			}
159		}
160		return nil
161	})
162	if err != nil {
163		return err
164	}
165
166	// Copy all files from android resources zip
167	r, err := zip.OpenReader(resZip)
168	if err != nil {
169		return err
170	}
171	for _, f := range r.File {
172		if strings.HasSuffix(f.Name, "AndroidManifest.xml") {
173			continue
174		}
175		fR, err := f.Open()
176		if err != nil {
177			return err
178		}
179		if err := ziputils.WriteReader(zipOut, fR, f.Name); err != nil {
180			return err
181		}
182	}
183	r.Close()
184
185	// Copy all native files from dummy native libs
186	if dummyNativeLibs != "" {
187		r, err := zip.OpenReader(dummyNativeLibs)
188		if err != nil {
189			return err
190		}
191		for _, f := range r.File {
192			if !strings.HasSuffix(f.Name, ".so") {
193				continue
194			}
195			fR, err := f.Open()
196			if err != nil {
197				return err
198			}
199			ziputils.WriteReader(zipOut, fR, f.Name)
200		}
201		r.Close()
202	}
203
204	if err := ziputils.WriteReader(zipOut, strings.NewReader(defJarManifestContents), jarManifestFileName); err != nil {
205		return err
206	}
207
208	if err := ziputils.WriteFile(zipOut, manifestPkgName, "package_name.txt"); err != nil {
209		return err
210	}
211	if err := ziputils.WriteFile(zipOut, inAppClassName, "app_name.txt"); err != nil {
212		return err
213	}
214	if linkedNativeLib != "" {
215		if err = ziputils.WriteFile(zipOut, linkedNativeLib, swigdepsFileName); err != nil {
216			return err
217		}
218	}
219	if arsc != "" {
220		if err = ziputils.WriteFile(zipOut, arsc, "resources.arsc"); err != nil {
221			return err
222		}
223	}
224
225	return nil
226}
227
228func extractDexes(dir, zipFile string) error {
229	r, err := zip.OpenReader(zipFile)
230	if err != nil {
231		return err
232	}
233	defer r.Close()
234
235	dexOut, err := os.Create(filepath.Join(dir, "classes.dex"))
236	if err != nil {
237		return err
238	}
239	defer dexOut.Close()
240	dexCount := 0
241	for _, f := range r.File {
242		if strings.HasSuffix(f.Name, ".dex") {
243			dexCount++
244			dR, err := f.Open()
245			if err != nil {
246				return err
247			}
248			io.Copy(dexOut, dR)
249		}
250	}
251	if dexCount != 1 {
252		return fmt.Errorf("expected 1 dex in %s, actually %d", dexFile, dexCount)
253	}
254	return nil
255}
256