• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
15// The dexpreopt package converts a global dexpreopt config and a module dexpreopt config into rules to perform
16// dexpreopting and to strip the dex files from the APK or JAR.
17//
18// It is used in two places; in the dexpeopt_gen binary for modules defined in Make, and directly linked into Soong.
19//
20// For Make modules it is built into the dexpreopt_gen binary, which is executed as a Make rule using global config and
21// module config specified in JSON files.  The binary writes out two shell scripts, only updating them if they have
22// changed.  One script takes an APK or JAR as an input and produces a zip file containing any outputs of preopting,
23// in the location they should be on the device.  The Make build rules will unzip the zip file into $(PRODUCT_OUT) when
24// installing the APK, which will install the preopt outputs into $(PRODUCT_OUT)/system or $(PRODUCT_OUT)/system_other
25// as necessary.  The zip file may be empty if preopting was disabled for any reason.  The second script takes an APK or
26// JAR as an input and strips the dex files in it as necessary.
27//
28// The intermediate shell scripts allow changes to this package or to the global config to regenerate the shell scripts
29// but only require re-executing preopting if the script has changed.
30//
31// For Soong modules this package is linked directly into Soong and run from the java package.  It generates the same
32// commands as for make, using athe same global config JSON file used by make, but using a module config structure
33// provided by Soong.  The generated commands are then converted into Soong rule and written directly to the ninja file,
34// with no extra shell scripts involved.
35package dexpreopt
36
37import (
38	"fmt"
39	"path/filepath"
40	"runtime"
41	"strings"
42
43	"android/soong/android"
44
45	"github.com/google/blueprint/pathtools"
46)
47
48const SystemPartition = "/system/"
49const SystemOtherPartition = "/system_other/"
50
51// GenerateStripRule generates a set of commands that will take an APK or JAR as an input and strip the dex files if
52// they are no longer necessary after preopting.
53func GenerateStripRule(global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
54	defer func() {
55		if r := recover(); r != nil {
56			if _, ok := r.(runtime.Error); ok {
57				panic(r)
58			} else if e, ok := r.(error); ok {
59				err = e
60				rule = nil
61			} else {
62				panic(r)
63			}
64		}
65	}()
66
67	tools := global.Tools
68
69	rule = android.NewRuleBuilder()
70
71	strip := shouldStripDex(module, global)
72
73	if strip {
74		if global.NeverAllowStripping {
75			panic(fmt.Errorf("Stripping requested on %q, though the product does not allow it", module.DexLocation))
76		}
77		// Only strips if the dex files are not already uncompressed
78		rule.Command().
79			Textf(`if (zipinfo %s '*.dex' 2>/dev/null | grep -v ' stor ' >/dev/null) ; then`, module.StripInputPath).
80			Tool(tools.Zip2zip).FlagWithInput("-i ", module.StripInputPath).FlagWithOutput("-o ", module.StripOutputPath).
81			FlagWithArg("-x ", `"classes*.dex"`).
82			Textf(`; else cp -f %s %s; fi`, module.StripInputPath, module.StripOutputPath)
83	} else {
84		rule.Command().Text("cp -f").Input(module.StripInputPath).Output(module.StripOutputPath)
85	}
86
87	return rule, nil
88}
89
90// GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a
91// ModuleConfig.  The produced files and their install locations will be available through rule.Installs().
92func GenerateDexpreoptRule(ctx android.PathContext,
93	global GlobalConfig, module ModuleConfig) (rule *android.RuleBuilder, err error) {
94
95	defer func() {
96		if r := recover(); r != nil {
97			if _, ok := r.(runtime.Error); ok {
98				panic(r)
99			} else if e, ok := r.(error); ok {
100				err = e
101				rule = nil
102			} else {
103				panic(r)
104			}
105		}
106	}()
107
108	rule = android.NewRuleBuilder()
109
110	generateProfile := module.ProfileClassListing.Valid() && !global.DisableGenerateProfile
111
112	var profile android.WritablePath
113	if generateProfile {
114		profile = profileCommand(ctx, global, module, rule)
115	}
116
117	if !dexpreoptDisabled(global, module) {
118		// Don't preopt individual boot jars, they will be preopted together.
119		// This check is outside dexpreoptDisabled because they still need to be stripped.
120		if !contains(global.BootJars, module.Name) {
121			appImage := (generateProfile || module.ForceCreateAppImage || global.DefaultAppImages) &&
122				!module.NoCreateAppImage
123
124			generateDM := shouldGenerateDM(module, global)
125
126			for i, arch := range module.Archs {
127				image := module.DexPreoptImages[i]
128				dexpreoptCommand(ctx, global, module, rule, arch, profile, image, appImage, generateDM)
129			}
130		}
131	}
132
133	return rule, nil
134}
135
136func dexpreoptDisabled(global GlobalConfig, module ModuleConfig) bool {
137	if contains(global.DisablePreoptModules, module.Name) {
138		return true
139	}
140
141	// If OnlyPreoptBootImageAndSystemServer=true and module is not in boot class path skip
142	// Also preopt system server jars since selinux prevents system server from loading anything from
143	// /data. If we don't do this they will need to be extracted which is not favorable for RAM usage
144	// or performance. If PreoptExtractedApk is true, we ignore the only preopt boot image options.
145	if global.OnlyPreoptBootImageAndSystemServer && !contains(global.BootJars, module.Name) &&
146		!contains(global.SystemServerJars, module.Name) && !module.PreoptExtractedApk {
147		return true
148	}
149
150	return false
151}
152
153func profileCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig,
154	rule *android.RuleBuilder) android.WritablePath {
155
156	profilePath := module.BuildPath.InSameDir(ctx, "profile.prof")
157	profileInstalledPath := module.DexLocation + ".prof"
158
159	if !module.ProfileIsTextListing {
160		rule.Command().FlagWithOutput("touch ", profilePath)
161	}
162
163	cmd := rule.Command().
164		Text(`ANDROID_LOG_TAGS="*:e"`).
165		Tool(global.Tools.Profman)
166
167	if module.ProfileIsTextListing {
168		// The profile is a test listing of classes (used for framework jars).
169		// We need to generate the actual binary profile before being able to compile.
170		cmd.FlagWithInput("--create-profile-from=", module.ProfileClassListing.Path())
171	} else {
172		// The profile is binary profile (used for apps). Run it through profman to
173		// ensure the profile keys match the apk.
174		cmd.
175			Flag("--copy-and-update-profile-key").
176			FlagWithInput("--profile-file=", module.ProfileClassListing.Path())
177	}
178
179	cmd.
180		FlagWithInput("--apk=", module.DexPath).
181		Flag("--dex-location="+module.DexLocation).
182		FlagWithOutput("--reference-profile-file=", profilePath)
183
184	if !module.ProfileIsTextListing {
185		cmd.Text(fmt.Sprintf(`|| echo "Profile out of date for %s"`, module.DexPath))
186	}
187	rule.Install(profilePath, profileInstalledPath)
188
189	return profilePath
190}
191
192func dexpreoptCommand(ctx android.PathContext, global GlobalConfig, module ModuleConfig, rule *android.RuleBuilder,
193	arch android.ArchType, profile, bootImage android.Path, appImage, generateDM bool) {
194
195	// HACK: make soname in Soong-generated .odex files match Make.
196	base := filepath.Base(module.DexLocation)
197	if filepath.Ext(base) == ".jar" {
198		base = "javalib.jar"
199	} else if filepath.Ext(base) == ".apk" {
200		base = "package.apk"
201	}
202
203	toOdexPath := func(path string) string {
204		return filepath.Join(
205			filepath.Dir(path),
206			"oat",
207			arch.String(),
208			pathtools.ReplaceExtension(filepath.Base(path), "odex"))
209	}
210
211	odexPath := module.BuildPath.InSameDir(ctx, "oat", arch.String(), pathtools.ReplaceExtension(base, "odex"))
212	odexInstallPath := toOdexPath(module.DexLocation)
213	if odexOnSystemOther(module, global) {
214		odexInstallPath = strings.Replace(odexInstallPath, SystemPartition, SystemOtherPartition, 1)
215	}
216
217	vdexPath := odexPath.ReplaceExtension(ctx, "vdex")
218	vdexInstallPath := pathtools.ReplaceExtension(odexInstallPath, "vdex")
219
220	invocationPath := odexPath.ReplaceExtension(ctx, "invocation")
221
222	// bootImage is .../dex_bootjars/system/framework/arm64/boot.art, but dex2oat wants
223	// .../dex_bootjars/system/framework/boot.art on the command line
224	var bootImageLocation string
225	if bootImage != nil {
226		bootImageLocation = PathToLocation(bootImage, arch)
227	}
228
229	// Lists of used and optional libraries from the build config to be verified against the manifest in the APK
230	var verifyUsesLibs []string
231	var verifyOptionalUsesLibs []string
232
233	// Lists of used and optional libraries from the build config, with optional libraries that are known to not
234	// be present in the current product removed.
235	var filteredUsesLibs []string
236	var filteredOptionalUsesLibs []string
237
238	// The class loader context using paths in the build
239	var classLoaderContextHost android.Paths
240
241	// The class loader context using paths as they will be on the device
242	var classLoaderContextTarget []string
243
244	// Extra paths that will be appended to the class loader if the APK manifest has targetSdkVersion < 28
245	var conditionalClassLoaderContextHost28 android.Paths
246	var conditionalClassLoaderContextTarget28 []string
247
248	// Extra paths that will be appended to the class loader if the APK manifest has targetSdkVersion < 29
249	var conditionalClassLoaderContextHost29 android.Paths
250	var conditionalClassLoaderContextTarget29 []string
251
252	var classLoaderContextHostString string
253
254	if module.EnforceUsesLibraries {
255		verifyUsesLibs = copyOf(module.UsesLibraries)
256		verifyOptionalUsesLibs = copyOf(module.OptionalUsesLibraries)
257
258		filteredOptionalUsesLibs = filterOut(global.MissingUsesLibraries, module.OptionalUsesLibraries)
259		filteredUsesLibs = append(copyOf(module.UsesLibraries), filteredOptionalUsesLibs...)
260
261		// Create class loader context for dex2oat from uses libraries and filtered optional libraries
262		for _, l := range filteredUsesLibs {
263
264			classLoaderContextHost = append(classLoaderContextHost,
265				pathForLibrary(module, l))
266			classLoaderContextTarget = append(classLoaderContextTarget,
267				filepath.Join("/system/framework", l+".jar"))
268		}
269
270		const httpLegacy = "org.apache.http.legacy"
271		const httpLegacyImpl = "org.apache.http.legacy.impl"
272
273		// Fix up org.apache.http.legacy.impl since it should be org.apache.http.legacy in the manifest.
274		replace(verifyUsesLibs, httpLegacyImpl, httpLegacy)
275		replace(verifyOptionalUsesLibs, httpLegacyImpl, httpLegacy)
276
277		if !contains(verifyUsesLibs, httpLegacy) && !contains(verifyOptionalUsesLibs, httpLegacy) {
278			conditionalClassLoaderContextHost28 = append(conditionalClassLoaderContextHost28,
279				pathForLibrary(module, httpLegacyImpl))
280			conditionalClassLoaderContextTarget28 = append(conditionalClassLoaderContextTarget28,
281				filepath.Join("/system/framework", httpLegacyImpl+".jar"))
282		}
283
284		const hidlBase = "android.hidl.base-V1.0-java"
285		const hidlManager = "android.hidl.manager-V1.0-java"
286
287		conditionalClassLoaderContextHost29 = append(conditionalClassLoaderContextHost29,
288			pathForLibrary(module, hidlManager))
289		conditionalClassLoaderContextTarget29 = append(conditionalClassLoaderContextTarget29,
290			filepath.Join("/system/framework", hidlManager+".jar"))
291		conditionalClassLoaderContextHost29 = append(conditionalClassLoaderContextHost29,
292			pathForLibrary(module, hidlBase))
293		conditionalClassLoaderContextTarget29 = append(conditionalClassLoaderContextTarget29,
294			filepath.Join("/system/framework", hidlBase+".jar"))
295
296		classLoaderContextHostString = strings.Join(classLoaderContextHost.Strings(), ":")
297	} else {
298		// Pass special class loader context to skip the classpath and collision check.
299		// This will get removed once LOCAL_USES_LIBRARIES is enforced.
300		// Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
301		// to the &.
302		classLoaderContextHostString = `\&`
303	}
304
305	rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String()))
306	rule.Command().FlagWithOutput("rm -f ", odexPath)
307	// Set values in the environment of the rule.  These may be modified by construct_context.sh.
308	rule.Command().FlagWithArg("class_loader_context_arg=--class-loader-context=", classLoaderContextHostString)
309	rule.Command().Text(`stored_class_loader_context_arg=""`)
310
311	if module.EnforceUsesLibraries {
312		rule.Command().Textf(`uses_library_names="%s"`, strings.Join(verifyUsesLibs, " "))
313		rule.Command().Textf(`optional_uses_library_names="%s"`, strings.Join(verifyOptionalUsesLibs, " "))
314		rule.Command().Textf(`aapt_binary="%s"`, global.Tools.Aapt)
315		rule.Command().Textf(`dex_preopt_host_libraries="%s"`,
316			strings.Join(classLoaderContextHost.Strings(), " ")).
317			Implicits(classLoaderContextHost)
318		rule.Command().Textf(`dex_preopt_target_libraries="%s"`,
319			strings.Join(classLoaderContextTarget, " "))
320		rule.Command().Textf(`conditional_host_libs_28="%s"`,
321			strings.Join(conditionalClassLoaderContextHost28.Strings(), " ")).
322			Implicits(conditionalClassLoaderContextHost28)
323		rule.Command().Textf(`conditional_target_libs_28="%s"`,
324			strings.Join(conditionalClassLoaderContextTarget28, " "))
325		rule.Command().Textf(`conditional_host_libs_29="%s"`,
326			strings.Join(conditionalClassLoaderContextHost29.Strings(), " ")).
327			Implicits(conditionalClassLoaderContextHost29)
328		rule.Command().Textf(`conditional_target_libs_29="%s"`,
329			strings.Join(conditionalClassLoaderContextTarget29, " "))
330		rule.Command().Text("source").Tool(global.Tools.VerifyUsesLibraries).Input(module.DexPath)
331		rule.Command().Text("source").Tool(global.Tools.ConstructContext)
332	}
333
334	// Devices that do not have a product partition use a symlink from /product to /system/product.
335	// Because on-device dexopt will see dex locations starting with /product, we change the paths
336	// to mimic this behavior.
337	dexLocationArg := module.DexLocation
338	if strings.HasPrefix(dexLocationArg, "/system/product/") {
339		dexLocationArg = strings.TrimPrefix(dexLocationArg, "/system")
340	}
341
342	cmd := rule.Command().
343		Text(`ANDROID_LOG_TAGS="*:e"`).
344		Tool(global.Tools.Dex2oat).
345		Flag("--avoid-storing-invocation").
346		FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath).
347		Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatXms).
348		Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatXmx).
349		Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", module.PreoptBootClassPathDexFiles, ":").
350		Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", module.PreoptBootClassPathDexLocations, ":").
351		Flag("${class_loader_context_arg}").
352		Flag("${stored_class_loader_context_arg}").
353		FlagWithArg("--boot-image=", bootImageLocation).Implicit(bootImage).
354		FlagWithInput("--dex-file=", module.DexPath).
355		FlagWithArg("--dex-location=", dexLocationArg).
356		FlagWithOutput("--oat-file=", odexPath).ImplicitOutput(vdexPath).
357		// Pass an empty directory, dex2oat shouldn't be reading arbitrary files
358		FlagWithArg("--android-root=", global.EmptyDirectory).
359		FlagWithArg("--instruction-set=", arch.String()).
360		FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]).
361		FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]).
362		Flag("--no-generate-debug-info").
363		Flag("--generate-build-id").
364		Flag("--abort-on-hard-verifier-error").
365		Flag("--force-determinism").
366		FlagWithArg("--no-inline-from=", "core-oj.jar")
367
368	var preoptFlags []string
369	if len(module.PreoptFlags) > 0 {
370		preoptFlags = module.PreoptFlags
371	} else if len(global.PreoptFlags) > 0 {
372		preoptFlags = global.PreoptFlags
373	}
374
375	if len(preoptFlags) > 0 {
376		cmd.Text(strings.Join(preoptFlags, " "))
377	}
378
379	if module.UncompressedDex {
380		cmd.FlagWithArg("--copy-dex-files=", "false")
381	}
382
383	if !anyHavePrefix(preoptFlags, "--compiler-filter=") {
384		var compilerFilter string
385		if contains(global.SystemServerJars, module.Name) {
386			// Jars of system server, use the product option if it is set, speed otherwise.
387			if global.SystemServerCompilerFilter != "" {
388				compilerFilter = global.SystemServerCompilerFilter
389			} else {
390				compilerFilter = "speed"
391			}
392		} else if contains(global.SpeedApps, module.Name) || contains(global.SystemServerApps, module.Name) {
393			// Apps loaded into system server, and apps the product default to being compiled with the
394			// 'speed' compiler filter.
395			compilerFilter = "speed"
396		} else if profile != nil {
397			// For non system server jars, use speed-profile when we have a profile.
398			compilerFilter = "speed-profile"
399		} else if global.DefaultCompilerFilter != "" {
400			compilerFilter = global.DefaultCompilerFilter
401		} else {
402			compilerFilter = "quicken"
403		}
404		cmd.FlagWithArg("--compiler-filter=", compilerFilter)
405	}
406
407	if generateDM {
408		cmd.FlagWithArg("--copy-dex-files=", "false")
409		dmPath := module.BuildPath.InSameDir(ctx, "generated.dm")
410		dmInstalledPath := pathtools.ReplaceExtension(module.DexLocation, "dm")
411		tmpPath := module.BuildPath.InSameDir(ctx, "primary.vdex")
412		rule.Command().Text("cp -f").Input(vdexPath).Output(tmpPath)
413		rule.Command().Tool(global.Tools.SoongZip).
414			FlagWithArg("-L", "9").
415			FlagWithOutput("-o", dmPath).
416			Flag("-j").
417			Input(tmpPath)
418		rule.Install(dmPath, dmInstalledPath)
419	}
420
421	// By default, emit debug info.
422	debugInfo := true
423	if global.NoDebugInfo {
424		// If the global setting suppresses mini-debug-info, disable it.
425		debugInfo = false
426	}
427
428	// PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
429	// PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO.
430	if contains(global.SystemServerJars, module.Name) {
431		if global.AlwaysSystemServerDebugInfo {
432			debugInfo = true
433		} else if global.NeverSystemServerDebugInfo {
434			debugInfo = false
435		}
436	} else {
437		if global.AlwaysOtherDebugInfo {
438			debugInfo = true
439		} else if global.NeverOtherDebugInfo {
440			debugInfo = false
441		}
442	}
443
444	// Never enable on eng.
445	if global.IsEng {
446		debugInfo = false
447	}
448
449	if debugInfo {
450		cmd.Flag("--generate-mini-debug-info")
451	} else {
452		cmd.Flag("--no-generate-mini-debug-info")
453	}
454
455	// Set the compiler reason to 'prebuilt' to identify the oat files produced
456	// during the build, as opposed to compiled on the device.
457	cmd.FlagWithArg("--compilation-reason=", "prebuilt")
458
459	if appImage {
460		appImagePath := odexPath.ReplaceExtension(ctx, "art")
461		appImageInstallPath := pathtools.ReplaceExtension(odexInstallPath, "art")
462		cmd.FlagWithOutput("--app-image-file=", appImagePath).
463			FlagWithArg("--image-format=", "lz4")
464		if !global.DontResolveStartupStrings {
465			cmd.FlagWithArg("--resolve-startup-const-strings=", "true")
466		}
467		rule.Install(appImagePath, appImageInstallPath)
468	}
469
470	if profile != nil {
471		cmd.FlagWithInput("--profile-file=", profile)
472	}
473
474	rule.Install(odexPath, odexInstallPath)
475	rule.Install(vdexPath, vdexInstallPath)
476}
477
478// Return if the dex file in the APK should be stripped.  If an APK is found to contain uncompressed dex files at
479// dex2oat time it will not be stripped even if strip=true.
480func shouldStripDex(module ModuleConfig, global GlobalConfig) bool {
481	strip := !global.DefaultNoStripping
482
483	if dexpreoptDisabled(global, module) {
484		strip = false
485	}
486
487	if module.NoStripping {
488		strip = false
489	}
490
491	// Don't strip modules that are not on the system partition in case the oat/vdex version in system ROM
492	// doesn't match the one in other partitions. It needs to be able to fall back to the APK for that case.
493	if !strings.HasPrefix(module.DexLocation, SystemPartition) {
494		strip = false
495	}
496
497	// system_other isn't there for an OTA, so don't strip if module is on system, and odex is on system_other.
498	if odexOnSystemOther(module, global) {
499		strip = false
500	}
501
502	if module.HasApkLibraries {
503		strip = false
504	}
505
506	// Don't strip with dex files we explicitly uncompress (dexopt will not store the dex code).
507	if module.UncompressedDex {
508		strip = false
509	}
510
511	if shouldGenerateDM(module, global) {
512		strip = false
513	}
514
515	if module.PresignedPrebuilt {
516		// Only strip out files if we can re-sign the package.
517		strip = false
518	}
519
520	return strip
521}
522
523func shouldGenerateDM(module ModuleConfig, global GlobalConfig) bool {
524	// Generating DM files only makes sense for verify, avoid doing for non verify compiler filter APKs.
525	// No reason to use a dm file if the dex is already uncompressed.
526	return global.GenerateDMFiles && !module.UncompressedDex &&
527		contains(module.PreoptFlags, "--compiler-filter=verify")
528}
529
530func OdexOnSystemOtherByName(name string, dexLocation string, global GlobalConfig) bool {
531	if !global.HasSystemOther {
532		return false
533	}
534
535	if global.SanitizeLite {
536		return false
537	}
538
539	if contains(global.SpeedApps, name) || contains(global.SystemServerApps, name) {
540		return false
541	}
542
543	for _, f := range global.PatternsOnSystemOther {
544		if makefileMatch(filepath.Join(SystemPartition, f), dexLocation) {
545			return true
546		}
547	}
548
549	return false
550}
551
552func odexOnSystemOther(module ModuleConfig, global GlobalConfig) bool {
553	return OdexOnSystemOtherByName(module.Name, module.DexLocation, global)
554}
555
556// PathToLocation converts .../system/framework/arm64/boot.art to .../system/framework/boot.art
557func PathToLocation(path android.Path, arch android.ArchType) string {
558	pathArch := filepath.Base(filepath.Dir(path.String()))
559	if pathArch != arch.String() {
560		panic(fmt.Errorf("last directory in %q must be %q", path, arch.String()))
561	}
562	return filepath.Join(filepath.Dir(filepath.Dir(path.String())), filepath.Base(path.String()))
563}
564
565func pathForLibrary(module ModuleConfig, lib string) android.Path {
566	path, ok := module.LibraryPaths[lib]
567	if !ok {
568		panic(fmt.Errorf("unknown library path for %q", lib))
569	}
570	return path
571}
572
573func makefileMatch(pattern, s string) bool {
574	percent := strings.IndexByte(pattern, '%')
575	switch percent {
576	case -1:
577		return pattern == s
578	case len(pattern) - 1:
579		return strings.HasPrefix(s, pattern[:len(pattern)-1])
580	default:
581		panic(fmt.Errorf("unsupported makefile pattern %q", pattern))
582	}
583}
584
585func contains(l []string, s string) bool {
586	for _, e := range l {
587		if e == s {
588			return true
589		}
590	}
591	return false
592}
593
594// remove all elements in a from b, returning a new slice
595func filterOut(a []string, b []string) []string {
596	var ret []string
597	for _, x := range b {
598		if !contains(a, x) {
599			ret = append(ret, x)
600		}
601	}
602	return ret
603}
604
605func replace(l []string, from, to string) {
606	for i := range l {
607		if l[i] == from {
608			l[i] = to
609		}
610	}
611}
612
613var copyOf = android.CopyOf
614
615func anyHavePrefix(l []string, prefix string) bool {
616	for _, x := range l {
617		if strings.HasPrefix(x, prefix) {
618			return true
619		}
620	}
621	return false
622}
623