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