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