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