• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2021 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package java
18
19import (
20	"fmt"
21	"github.com/google/blueprint"
22	"github.com/google/blueprint/proptools"
23	"strings"
24
25	"android/soong/android"
26)
27
28// Build rules and utilities to generate individual packages/modules/common/proto/classpaths.proto
29// config files based on build configuration to embed into /system and /apex on a device.
30//
31// See `derive_classpath` service that reads the configs at runtime and defines *CLASSPATH variables
32// on the device.
33
34type classpathType int
35
36const (
37	// Matches definition in packages/modules/common/proto/classpaths.proto
38	BOOTCLASSPATH classpathType = iota
39	DEX2OATBOOTCLASSPATH
40	SYSTEMSERVERCLASSPATH
41	STANDALONE_SYSTEMSERVER_JARS
42)
43
44func (c classpathType) String() string {
45	return [...]string{"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"}[c]
46}
47
48type classpathFragmentProperties struct {
49	// Whether to generated classpaths.proto config instance for the fragment. If the config is not
50	// generated, then relevant boot jars are added to platform classpath, i.e. platform_bootclasspath
51	// or platform_systemserverclasspath. This is useful for non-updatable APEX boot jars, to keep
52	// them as part of dexopt on device. Defaults to true.
53	Generate_classpaths_proto *bool
54}
55
56// classpathFragment interface is implemented by a module that contributes jars to a *CLASSPATH
57// variables at runtime.
58type classpathFragment interface {
59	android.Module
60
61	classpathFragmentBase() *ClasspathFragmentBase
62}
63
64// ClasspathFragmentBase is meant to be embedded in any module types that implement classpathFragment;
65// such modules are expected to call initClasspathFragment().
66type ClasspathFragmentBase struct {
67	properties classpathFragmentProperties
68
69	classpathType classpathType
70
71	outputFilepath android.OutputPath
72	installDirPath android.InstallPath
73}
74
75func (c *ClasspathFragmentBase) classpathFragmentBase() *ClasspathFragmentBase {
76	return c
77}
78
79// Initializes ClasspathFragmentBase struct. Must be called by all modules that include ClasspathFragmentBase.
80func initClasspathFragment(c classpathFragment, classpathType classpathType) {
81	base := c.classpathFragmentBase()
82	base.classpathType = classpathType
83	c.AddProperties(&base.properties)
84}
85
86// Matches definition of Jar in packages/modules/SdkExtensions/proto/classpaths.proto
87type classpathJar struct {
88	path          string
89	classpath     classpathType
90	minSdkVersion string
91	maxSdkVersion string
92}
93
94// gatherPossibleApexModuleNamesAndStems returns a set of module and stem names from the
95// supplied contents that may be in the apex boot jars.
96//
97// The module names are included because sometimes the stem is set to just change the name of
98// the installed file and it expects the configuration to still use the actual module name.
99//
100// The stem names are included because sometimes the stem is set to change the effective name of the
101// module that is used in the configuration as well,e .g. when a test library is overriding an
102// actual boot jar
103func gatherPossibleApexModuleNamesAndStems(ctx android.ModuleContext, contents []string, tag blueprint.DependencyTag) []string {
104	set := map[string]struct{}{}
105	for _, name := range contents {
106		dep := ctx.GetDirectDepWithTag(name, tag)
107		set[name] = struct{}{}
108		if m, ok := dep.(ModuleWithStem); ok {
109			set[m.Stem()] = struct{}{}
110		} else {
111			ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name)
112		}
113	}
114	return android.SortedStringKeys(set)
115}
116
117// Converts android.ConfiguredJarList into a list of classpathJars for each given classpathType.
118func configuredJarListToClasspathJars(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, classpaths ...classpathType) []classpathJar {
119	paths := configuredJars.DevicePaths(ctx.Config(), android.Android)
120	jars := make([]classpathJar, 0, len(paths)*len(classpaths))
121	for i := 0; i < len(paths); i++ {
122		for _, classpathType := range classpaths {
123			jar := classpathJar{
124				classpath: classpathType,
125				path:      paths[i],
126			}
127			ctx.VisitDirectDepsIf(func(m android.Module) bool {
128				return m.Name() == configuredJars.Jar(i)
129			}, func(m android.Module) {
130				if s, ok := m.(*SdkLibrary); ok {
131					// TODO(208456999): instead of mapping "current" to latest, min_sdk_version should never be set to "current"
132					if s.minSdkVersion.Specified() {
133						if s.minSdkVersion.ApiLevel.IsCurrent() {
134							jar.minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
135						} else {
136							jar.minSdkVersion = s.minSdkVersion.ApiLevel.String()
137						}
138					}
139					if s.maxSdkVersion.Specified() {
140						if s.maxSdkVersion.ApiLevel.IsCurrent() {
141							jar.maxSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
142						} else {
143							jar.maxSdkVersion = s.maxSdkVersion.ApiLevel.String()
144						}
145					}
146				}
147			})
148			jars = append(jars, jar)
149		}
150	}
151	return jars
152}
153
154func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, jars []classpathJar) {
155	generateProto := proptools.BoolDefault(c.properties.Generate_classpaths_proto, true)
156	if generateProto {
157		outputFilename := strings.ToLower(c.classpathType.String()) + ".pb"
158		c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
159		c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
160
161		generatedTextproto := android.PathForModuleOut(ctx, outputFilename+".textproto")
162		writeClasspathsTextproto(ctx, generatedTextproto, jars)
163
164		rule := android.NewRuleBuilder(pctx, ctx)
165		rule.Command().
166			BuiltTool("conv_classpaths_proto").
167			Flag("encode").
168			Flag("--format=textproto").
169			FlagWithInput("--input=", generatedTextproto).
170			FlagWithOutput("--output=", c.outputFilepath)
171
172		rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
173	}
174
175	classpathProtoInfo := ClasspathFragmentProtoContentInfo{
176		ClasspathFragmentProtoGenerated:  generateProto,
177		ClasspathFragmentProtoContents:   configuredJars,
178		ClasspathFragmentProtoInstallDir: c.installDirPath,
179		ClasspathFragmentProtoOutput:     c.outputFilepath,
180	}
181	ctx.SetProvider(ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo)
182}
183
184func writeClasspathsTextproto(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
185	var content strings.Builder
186
187	for _, jar := range jars {
188		fmt.Fprintf(&content, "jars {\n")
189		fmt.Fprintf(&content, "path: \"%s\"\n", jar.path)
190		fmt.Fprintf(&content, "classpath: %s\n", jar.classpath)
191		fmt.Fprintf(&content, "min_sdk_version: \"%s\"\n", jar.minSdkVersion)
192		fmt.Fprintf(&content, "max_sdk_version: \"%s\"\n", jar.maxSdkVersion)
193		fmt.Fprintf(&content, "}\n")
194	}
195
196	android.WriteFileRule(ctx, output, content.String())
197}
198
199// Returns AndroidMkEntries objects to install generated classpath.proto.
200// Do not use this to install into APEXes as the injection of the generated files happen separately for APEXes.
201func (c *ClasspathFragmentBase) androidMkEntries() []android.AndroidMkEntries {
202	return []android.AndroidMkEntries{{
203		Class:      "ETC",
204		OutputFile: android.OptionalPathForPath(c.outputFilepath),
205		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
206			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
207				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.String())
208				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base())
209			},
210		},
211	}}
212}
213
214var ClasspathFragmentProtoContentInfoProvider = blueprint.NewProvider(ClasspathFragmentProtoContentInfo{})
215
216type ClasspathFragmentProtoContentInfo struct {
217	// Whether the classpaths.proto config is generated for the fragment.
218	ClasspathFragmentProtoGenerated bool
219
220	// ClasspathFragmentProtoContents contains a list of jars that are part of this classpath fragment.
221	ClasspathFragmentProtoContents android.ConfiguredJarList
222
223	// ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module.
224	//
225	// The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir
226	// for more details.
227	ClasspathFragmentProtoOutput android.OutputPath
228
229	// ClasspathFragmentProtoInstallDir contains information about on device location for the generated classpaths.proto file.
230	//
231	// The path encodes expected sub-location within partitions, i.e. etc/classpaths/<proto-file>,
232	// for ClasspathFragmentProtoOutput. To get sub-location, instead of the full output / make path
233	// use android.InstallPath#Rel().
234	//
235	// This is only relevant for APEX modules as they perform their own installation; while regular
236	// system files are installed via ClasspathFragmentBase#androidMkEntries().
237	ClasspathFragmentProtoInstallDir android.InstallPath
238}
239