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	"strings"
23
24	"android/soong/android"
25)
26
27// Build rules and utilities to generate individual packages/modules/SdkExtensions/proto/classpaths.proto
28// config files based on build configuration to embed into /system and /apex on a device.
29//
30// See `derive_classpath` service that reads the configs at runtime and defines *CLASSPATH variables
31// on the device.
32
33type classpathType int
34
35const (
36	// Matches definition in packages/modules/SdkExtensions/proto/classpaths.proto
37	BOOTCLASSPATH classpathType = iota
38	DEX2OATBOOTCLASSPATH
39	SYSTEMSERVERCLASSPATH
40)
41
42func (c classpathType) String() string {
43	return [...]string{"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "SYSTEMSERVERCLASSPATH"}[c]
44}
45
46type classpathFragmentProperties struct {
47}
48
49// classpathFragment interface is implemented by a module that contributes jars to a *CLASSPATH
50// variables at runtime.
51type classpathFragment interface {
52	android.Module
53
54	classpathFragmentBase() *ClasspathFragmentBase
55
56	// ClasspathFragmentToConfiguredJarList returns android.ConfiguredJarList representation of all
57	// the jars in this classpath fragment.
58	ClasspathFragmentToConfiguredJarList(ctx android.ModuleContext) android.ConfiguredJarList
59}
60
61// ClasspathFragmentBase is meant to be embedded in any module types that implement classpathFragment;
62// such modules are expected to call initClasspathFragment().
63type ClasspathFragmentBase struct {
64	properties classpathFragmentProperties
65
66	classpathType classpathType
67
68	outputFilepath android.OutputPath
69	installDirPath android.InstallPath
70}
71
72func (c *ClasspathFragmentBase) classpathFragmentBase() *ClasspathFragmentBase {
73	return c
74}
75
76// Initializes ClasspathFragmentBase struct. Must be called by all modules that include ClasspathFragmentBase.
77func initClasspathFragment(c classpathFragment, classpathType classpathType) {
78	base := c.classpathFragmentBase()
79	base.classpathType = classpathType
80	c.AddProperties(&base.properties)
81}
82
83// Matches definition of Jar in packages/modules/SdkExtensions/proto/classpaths.proto
84type classpathJar struct {
85	path      string
86	classpath classpathType
87	// TODO(satayev): propagate min/max sdk versions for the jars
88	minSdkVersion int32
89	maxSdkVersion int32
90}
91
92// gatherPossibleUpdatableModuleNamesAndStems returns a set of module and stem names from the
93// supplied contents that may be in the updatable boot jars.
94//
95// The module names are included because sometimes the stem is set to just change the name of
96// the installed file and it expects the configuration to still use the actual module name.
97//
98// The stem names are included because sometimes the stem is set to change the effective name of the
99// module that is used in the configuration as well,e .g. when a test library is overriding an
100// actual boot jar
101func gatherPossibleUpdatableModuleNamesAndStems(ctx android.ModuleContext, contents []string, tag blueprint.DependencyTag) []string {
102	set := map[string]struct{}{}
103	for _, name := range contents {
104		dep := ctx.GetDirectDepWithTag(name, tag)
105		set[name] = struct{}{}
106		if m, ok := dep.(ModuleWithStem); ok {
107			set[m.Stem()] = struct{}{}
108		} else {
109			ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name)
110		}
111	}
112	return android.SortedStringKeys(set)
113}
114
115// Converts android.ConfiguredJarList into a list of classpathJars for each given classpathType.
116func configuredJarListToClasspathJars(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, classpaths ...classpathType) []classpathJar {
117	paths := configuredJars.DevicePaths(ctx.Config(), android.Android)
118	jars := make([]classpathJar, 0, len(paths)*len(classpaths))
119	for i := 0; i < len(paths); i++ {
120		for _, classpathType := range classpaths {
121			jars = append(jars, classpathJar{
122				classpath: classpathType,
123				path:      paths[i],
124			})
125		}
126	}
127	return jars
128}
129
130func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, jars []classpathJar) {
131	outputFilename := strings.ToLower(c.classpathType.String()) + ".pb"
132	c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath
133	c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths")
134
135	generatedJson := android.PathForModuleOut(ctx, outputFilename+".json")
136	writeClasspathsJson(ctx, generatedJson, jars)
137
138	rule := android.NewRuleBuilder(pctx, ctx)
139	rule.Command().
140		BuiltTool("conv_classpaths_proto").
141		Flag("encode").
142		Flag("--format=json").
143		FlagWithInput("--input=", generatedJson).
144		FlagWithOutput("--output=", c.outputFilepath)
145
146	rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String())
147
148	classpathProtoInfo := ClasspathFragmentProtoContentInfo{
149		ClasspathFragmentProtoInstallDir: c.installDirPath,
150		ClasspathFragmentProtoOutput:     c.outputFilepath,
151	}
152	ctx.SetProvider(ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo)
153}
154
155func writeClasspathsJson(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) {
156	var content strings.Builder
157	fmt.Fprintf(&content, "{\n")
158	fmt.Fprintf(&content, "\"jars\": [\n")
159	for idx, jar := range jars {
160		fmt.Fprintf(&content, "{\n")
161
162		fmt.Fprintf(&content, "\"path\": \"%s\",\n", jar.path)
163		fmt.Fprintf(&content, "\"classpath\": \"%s\"\n", jar.classpath)
164
165		if idx < len(jars)-1 {
166			fmt.Fprintf(&content, "},\n")
167		} else {
168			fmt.Fprintf(&content, "}\n")
169		}
170	}
171	fmt.Fprintf(&content, "]\n")
172	fmt.Fprintf(&content, "}\n")
173	android.WriteFileRule(ctx, output, content.String())
174}
175
176// Returns AndroidMkEntries objects to install generated classpath.proto.
177// Do not use this to install into APEXes as the injection of the generated files happen separately for APEXes.
178func (c *ClasspathFragmentBase) androidMkEntries() []android.AndroidMkEntries {
179	return []android.AndroidMkEntries{{
180		Class:      "ETC",
181		OutputFile: android.OptionalPathForPath(c.outputFilepath),
182		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
183			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
184				entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.ToMakePath().String())
185				entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base())
186			},
187		},
188	}}
189}
190
191var ClasspathFragmentProtoContentInfoProvider = blueprint.NewProvider(ClasspathFragmentProtoContentInfo{})
192
193type ClasspathFragmentProtoContentInfo struct {
194	// ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module.
195	//
196	// The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir
197	// for more details.
198	ClasspathFragmentProtoOutput android.OutputPath
199
200	// ClasspathFragmentProtoInstallDir contains information about on device location for the generated classpaths.proto file.
201	//
202	// The path encodes expected sub-location within partitions, i.e. etc/classpaths/<proto-file>,
203	// for ClasspathFragmentProtoOutput. To get sub-location, instead of the full output / make path
204	// use android.InstallPath#Rel().
205	//
206	// This is only relevant for APEX modules as they perform their own installation; while regular
207	// system files are installed via ClasspathFragmentBase#androidMkEntries().
208	ClasspathFragmentProtoInstallDir android.InstallPath
209}
210