1// Copyright (C) 2021 The Android Open Source Project
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	"fmt"
19	"strings"
20
21	"github.com/google/blueprint"
22)
23
24// Provides support for interacting with the `deapexer` module to which a `prebuilt_apex` module
25// will delegate the work to export files from a prebuilt '.apex` file.
26//
27// The actual processing that is done is quite convoluted but it is all about combining information
28// from multiple different sources in order to allow a prebuilt module to use a file extracted from
29// an apex file. As follows:
30//
31// 1. A prebuilt module, e.g. prebuilt_bootclasspath_fragment or java_import needs to use a file
32//    from a prebuilt_apex/apex_set. It knows the path of the file within the apex but does not know
33//    where the apex file is or what apex to use.
34//
35// 2. The connection between the prebuilt module and the prebuilt_apex/apex_set is created through
36//    use of an exported_... property on the latter. That causes four things to occur:
37//    a. A `deapexer` mopdule is created by the prebuilt_apex/apex_set to extract files from the
38//       apex file.
39//    b. A dependency is added from the prebuilt_apex/apex_set modules onto the prebuilt modules
40//       listed in those properties.
41//    c. An APEX variant is created for each of those prebuilt modules.
42//    d. A dependency is added from the prebuilt modules to the `deapexer` module.
43//
44// 3. The prebuilt_apex/apex_set modules do not know which files are available in the apex file.
45//    That information could be specified on the prebuilt_apex/apex_set modules but without
46//    automated generation of those modules it would be expensive to maintain. So, instead they
47//    obtain that information from the prebuilt modules. They do not know what files are actually in
48//    the apex file either but they know what files they need from it. So, the
49//    prebuilt_apex/apex_set modules obtain the files that should be in the apex file from those
50//    modules and then pass those onto the `deapexer` module.
51//
52// 4. The `deapexer` module's ninja rule extracts all the files from the apex file into an output
53//    directory and checks that all the expected files are there. The expected files are declared as
54//    the outputs of the ninja rule so they are available to other modules.
55//
56// 5. The prebuilt modules then retrieve the paths to the files that they needed from the `deapexer`
57//    module.
58//
59// The files that are passed to `deapexer` and those that are passed back have a unique identifier
60// that links them together. e.g. If the `deapexer` is passed something like this:
61//     javalib/core-libart.jar -> javalib/core-libart.jar
62// it will return something like this:
63//     javalib/core-libart.jar -> out/soong/.....deapexer.../javalib/core-libart.jar
64//
65// The reason why the `deapexer` module is separate from the prebuilt_apex/apex_set is to avoid
66// cycles. e.g.
67//   prebuilt_apex "com.android.art" depends upon java_import "core-libart":
68//       This is so it can create an APEX variant of the latter and obtain information about the
69//       files that it needs from the apex file.
70//   java_import "core-libart" depends upon `deapexer` module:
71//       This is so it can retrieve the paths to the files it needs.
72
73// The information exported by the `deapexer` module, access it using `DeapxerInfoProvider`.
74type DeapexerInfo struct {
75	apexModuleName string
76
77	// map from the name of an exported file from a prebuilt_apex to the path to that file. The
78	// exported file name is the apex relative path, e.g. javalib/core-libart.jar.
79	//
80	// See Prebuilt.ApexInfoMutator for more information.
81	exports map[string]WritablePath
82
83	// name of the java libraries exported from the apex
84	// e.g. core-libart
85	exportedModuleNames []string
86
87	// name of the java libraries exported from the apex that should be dexpreopt'd with the .prof
88	// file embedded in the apex
89	dexpreoptProfileGuidedExportedModuleNames []string
90}
91
92// ApexModuleName returns the name of the APEX module that provided the info.
93func (i DeapexerInfo) ApexModuleName() string {
94	return i.apexModuleName
95}
96
97// PrebuiltExportPath provides the path, or nil if not available, of a file exported from the
98// prebuilt_apex that created this ApexInfo.
99//
100// The exported file is identified by the apex relative path, e.g. "javalib/core-libart.jar".
101//
102// See apex/deapexer.go for more information.
103func (i DeapexerInfo) PrebuiltExportPath(apexRelativePath string) WritablePath {
104	path := i.exports[apexRelativePath]
105	return path
106}
107
108func (i DeapexerInfo) GetExportedModuleNames() []string {
109	return i.exportedModuleNames
110}
111
112// Provider that can be used from within the `GenerateAndroidBuildActions` of a module that depends
113// on a `deapexer` module to retrieve its `DeapexerInfo`.
114var DeapexerProvider = blueprint.NewProvider[DeapexerInfo]()
115
116// NewDeapexerInfo creates and initializes a DeapexerInfo that is suitable
117// for use with a prebuilt_apex module.
118//
119// See apex/deapexer.go for more information.
120func NewDeapexerInfo(apexModuleName string, exports map[string]WritablePath, moduleNames []string) DeapexerInfo {
121	return DeapexerInfo{
122		apexModuleName:      apexModuleName,
123		exports:             exports,
124		exportedModuleNames: moduleNames,
125	}
126}
127
128func (i *DeapexerInfo) GetDexpreoptProfileGuidedExportedModuleNames() []string {
129	return i.dexpreoptProfileGuidedExportedModuleNames
130}
131
132func (i *DeapexerInfo) AddDexpreoptProfileGuidedExportedModuleNames(names ...string) {
133	i.dexpreoptProfileGuidedExportedModuleNames = append(i.dexpreoptProfileGuidedExportedModuleNames, names...)
134}
135
136type deapexerTagStruct struct {
137	blueprint.BaseDependencyTag
138}
139
140// Mark this tag so dependencies that use it are excluded from APEX contents.
141func (t deapexerTagStruct) ExcludeFromApexContents() {}
142
143var _ ExcludeFromApexContentsTag = DeapexerTag
144
145// A tag that is used for dependencies on the `deapexer` module.
146var DeapexerTag = deapexerTagStruct{}
147
148// RequiredFilesFromPrebuiltApex must be implemented by modules that require files to be exported
149// from a prebuilt_apex/apex_set.
150type RequiredFilesFromPrebuiltApex interface {
151	// RequiredFilesFromPrebuiltApex returns a list of the file paths (relative to the root of the
152	// APEX's contents) that the implementing module requires from within a prebuilt .apex file.
153	//
154	// For each file path this will cause the file to be extracted out of the prebuilt .apex file, and
155	// the path to the extracted file will be stored in the DeapexerInfo using the APEX relative file
156	// path as the key, The path can then be retrieved using the PrebuiltExportPath(key) method.
157	RequiredFilesFromPrebuiltApex(ctx BaseModuleContext) []string
158
159	// Returns true if a transitive dependency of an apex should use a .prof file to guide dexpreopt
160	UseProfileGuidedDexpreopt() bool
161}
162
163// Marker interface that identifies dependencies on modules that may require files from a prebuilt
164// apex.
165type RequiresFilesFromPrebuiltApexTag interface {
166	blueprint.DependencyTag
167
168	// Method that differentiates this interface from others.
169	RequiresFilesFromPrebuiltApex()
170}
171
172// FindDeapexerProviderForModule searches through the direct dependencies of the current context
173// module for a DeapexerTag dependency and returns its DeapexerInfo. If a single nonambiguous
174// deapexer module isn't found then it returns it an error
175// clients should check the value of error and call ctx.ModuleErrof if a non nil error is received
176func FindDeapexerProviderForModule(ctx ModuleContext) (*DeapexerInfo, error) {
177	var di *DeapexerInfo
178	var err error
179	ctx.VisitDirectDepsWithTag(DeapexerTag, func(m Module) {
180		if err != nil {
181			// An err has been found. Do not visit further.
182			return
183		}
184		c, _ := OtherModuleProvider(ctx, m, DeapexerProvider)
185		p := &c
186		if di != nil {
187			// If two DeapexerInfo providers have been found then check if they are
188			// equivalent. If they are then use the selected one, otherwise fail.
189			if selected := equivalentDeapexerInfoProviders(di, p); selected != nil {
190				di = selected
191				return
192			}
193			err = fmt.Errorf("Multiple installable prebuilt APEXes provide ambiguous deapexers: %s and %s", di.ApexModuleName(), p.ApexModuleName())
194		}
195		di = p
196	})
197	if err != nil {
198		return nil, err
199	}
200	if di != nil {
201		return di, nil
202	}
203	ai, _ := ModuleProvider(ctx, ApexInfoProvider)
204	return nil, fmt.Errorf("No prebuilt APEX provides a deapexer module for APEX variant %s", ai.ApexVariationName)
205}
206
207// removeCompressedApexSuffix removes the _compressed suffix from the name if present.
208func removeCompressedApexSuffix(name string) string {
209	return strings.TrimSuffix(name, "_compressed")
210}
211
212// equivalentDeapexerInfoProviders checks to make sure that the two DeapexerInfo structures are
213// equivalent.
214//
215// At the moment <x> and <x>_compressed APEXes are treated as being equivalent.
216//
217// If they are not equivalent then this returns nil, otherwise, this returns the DeapexerInfo that
218// should be used by the build, which is always the uncompressed one. That ensures that the behavior
219// of the build is not dependent on which prebuilt APEX is visited first.
220func equivalentDeapexerInfoProviders(p1 *DeapexerInfo, p2 *DeapexerInfo) *DeapexerInfo {
221	n1 := removeCompressedApexSuffix(p1.ApexModuleName())
222	n2 := removeCompressedApexSuffix(p2.ApexModuleName())
223
224	// If the names don't match then they are not equivalent.
225	if n1 != n2 {
226		return nil
227	}
228
229	// Select the uncompressed APEX.
230	if n1 == removeCompressedApexSuffix(n1) {
231		return p1
232	} else {
233		return p2
234	}
235}
236