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).(android.Module)
107		set[ModuleStemForDeapexing(dep)] = 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.SortedKeys(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					minSdkVersion := s.MinSdkVersion(ctx)
132					maxSdkVersion := s.MaxSdkVersion(ctx)
133					// TODO(208456999): instead of mapping "current" to latest, min_sdk_version should never be set to "current"
134					if minSdkVersion.Specified() {
135						if minSdkVersion.IsCurrent() {
136							jar.minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
137						} else {
138							jar.minSdkVersion = minSdkVersion.String()
139						}
140					}
141					if maxSdkVersion.Specified() {
142						if maxSdkVersion.IsCurrent() {
143							jar.maxSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String()
144						} else {
145							jar.maxSdkVersion = maxSdkVersion.String()
146						}
147					}
148				}
149			})
150			jars = append(jars, jar)
151		}
152	}
153	return jars
154}
155
156func (c *ClasspathFragmentBase) outputFilename() string {
157	return strings.ToLower(c.classpathType.String()) + ".pb"
158}
159
160func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, jars []classpathJar) {
161	generateProto := proptools.BoolDefault(c.properties.Generate_classpaths_proto, true)
162	if generateProto {
163		outputFilename := c.outputFilename()
164		c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
165		c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
166
167		generatedTextproto := android.PathForModuleOut(ctx, outputFilename+".textproto")
168		writeClasspathsTextproto(ctx, generatedTextproto, jars)
169
170		rule := android.NewRuleBuilder(pctx, ctx)
171		rule.Command().
172			BuiltTool("conv_classpaths_proto").
173			Flag("encode").
174			Flag("--format=textproto").
175			FlagWithInput("--input=", generatedTextproto).
176			FlagWithOutput("--output=", c.outputFilepath)
177
178		rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
179	}
180
181	classpathProtoInfo := ClasspathFragmentProtoContentInfo{
182		ClasspathFragmentProtoGenerated:  generateProto,
183		ClasspathFragmentProtoContents:   configuredJars,
184		ClasspathFragmentProtoInstallDir: c.installDirPath,
185		ClasspathFragmentProtoOutput:     c.outputFilepath,
186	}
187	android.SetProvider(ctx, ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo)
188}
189
190func (c *ClasspathFragmentBase) installClasspathProto(ctx android.ModuleContext) android.InstallPath {
191	return ctx.InstallFile(c.installDirPath, c.outputFilename(), c.outputFilepath)
192}
193
194func writeClasspathsTextproto(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
195	var content strings.Builder
196
197	for _, jar := range jars {
198		fmt.Fprintf(&content, "jars {\n")
199		fmt.Fprintf(&content, "path: \"%s\"\n", jar.path)
200		fmt.Fprintf(&content, "classpath: %s\n", jar.classpath)
201		fmt.Fprintf(&content, "min_sdk_version: \"%s\"\n", jar.minSdkVersion)
202		fmt.Fprintf(&content, "max_sdk_version: \"%s\"\n", jar.maxSdkVersion)
203		fmt.Fprintf(&content, "}\n")
204	}
205
206	android.WriteFileRule(ctx, output, content.String())
207}
208
209// Returns AndroidMkEntries objects to install generated classpath.proto.
210// Do not use this to install into APEXes as the injection of the generated files happen separately for APEXes.
211func (c *ClasspathFragmentBase) androidMkEntries() []android.AndroidMkEntries {
212	return []android.AndroidMkEntries{{
213		Class:      "ETC",
214		OutputFile: android.OptionalPathForPath(c.outputFilepath),
215		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
216			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
217				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.String())
218				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base())
219			},
220		},
221	}}
222}
223
224var ClasspathFragmentProtoContentInfoProvider = blueprint.NewProvider[ClasspathFragmentProtoContentInfo]()
225
226type ClasspathFragmentProtoContentInfo struct {
227	// Whether the classpaths.proto config is generated for the fragment.
228	ClasspathFragmentProtoGenerated bool
229
230	// ClasspathFragmentProtoContents contains a list of jars that are part of this classpath fragment.
231	ClasspathFragmentProtoContents android.ConfiguredJarList
232
233	// ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module.
234	//
235	// The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir
236	// for more details.
237	ClasspathFragmentProtoOutput android.OutputPath
238
239	// ClasspathFragmentProtoInstallDir contains information about on device location for the generated classpaths.proto file.
240	//
241	// The path encodes expected sub-location within partitions, i.e. etc/classpaths/<proto-file>,
242	// for ClasspathFragmentProtoOutput. To get sub-location, instead of the full output / make path
243	// use android.InstallPath#Rel().
244	//
245	// This is only relevant for APEX modules as they perform their own installation; while regular
246	// system files are installed via ClasspathFragmentBase#androidMkEntries().
247	ClasspathFragmentProtoInstallDir android.InstallPath
248}
249