/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package java import ( "fmt" "github.com/google/blueprint" "github.com/google/blueprint/proptools" "strings" "android/soong/android" ) // Build rules and utilities to generate individual packages/modules/common/proto/classpaths.proto // config files based on build configuration to embed into /system and /apex on a device. // // See `derive_classpath` service that reads the configs at runtime and defines *CLASSPATH variables // on the device. type classpathType int const ( // Matches definition in packages/modules/common/proto/classpaths.proto BOOTCLASSPATH classpathType = iota DEX2OATBOOTCLASSPATH SYSTEMSERVERCLASSPATH STANDALONE_SYSTEMSERVER_JARS ) func (c classpathType) String() string { return [...]string{"BOOTCLASSPATH", "DEX2OATBOOTCLASSPATH", "SYSTEMSERVERCLASSPATH", "STANDALONE_SYSTEMSERVER_JARS"}[c] } type classpathFragmentProperties struct { // Whether to generated classpaths.proto config instance for the fragment. If the config is not // generated, then relevant boot jars are added to platform classpath, i.e. platform_bootclasspath // or platform_systemserverclasspath. This is useful for non-updatable APEX boot jars, to keep // them as part of dexopt on device. Defaults to true. Generate_classpaths_proto *bool } // classpathFragment interface is implemented by a module that contributes jars to a *CLASSPATH // variables at runtime. type classpathFragment interface { android.Module classpathFragmentBase() *ClasspathFragmentBase } // ClasspathFragmentBase is meant to be embedded in any module types that implement classpathFragment; // such modules are expected to call initClasspathFragment(). type ClasspathFragmentBase struct { properties classpathFragmentProperties classpathType classpathType outputFilepath android.OutputPath installDirPath android.InstallPath } func (c *ClasspathFragmentBase) classpathFragmentBase() *ClasspathFragmentBase { return c } // Initializes ClasspathFragmentBase struct. Must be called by all modules that include ClasspathFragmentBase. func initClasspathFragment(c classpathFragment, classpathType classpathType) { base := c.classpathFragmentBase() base.classpathType = classpathType c.AddProperties(&base.properties) } // Matches definition of Jar in packages/modules/SdkExtensions/proto/classpaths.proto type classpathJar struct { path string classpath classpathType minSdkVersion string maxSdkVersion string } // gatherPossibleApexModuleNamesAndStems returns a set of module and stem names from the // supplied contents that may be in the apex boot jars. // // The module names are included because sometimes the stem is set to just change the name of // the installed file and it expects the configuration to still use the actual module name. // // The stem names are included because sometimes the stem is set to change the effective name of the // module that is used in the configuration as well,e .g. when a test library is overriding an // actual boot jar func gatherPossibleApexModuleNamesAndStems(ctx android.ModuleContext, contents []string, tag blueprint.DependencyTag) []string { set := map[string]struct{}{} for _, name := range contents { dep := ctx.GetDirectDepWithTag(name, tag) set[name] = struct{}{} if m, ok := dep.(ModuleWithStem); ok { set[m.Stem()] = struct{}{} } else { ctx.PropertyErrorf("contents", "%v is not a ModuleWithStem", name) } } return android.SortedKeys(set) } // Converts android.ConfiguredJarList into a list of classpathJars for each given classpathType. func configuredJarListToClasspathJars(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, classpaths ...classpathType) []classpathJar { paths := configuredJars.DevicePaths(ctx.Config(), android.Android) jars := make([]classpathJar, 0, len(paths)*len(classpaths)) for i := 0; i < len(paths); i++ { for _, classpathType := range classpaths { jar := classpathJar{ classpath: classpathType, path: paths[i], } ctx.VisitDirectDepsIf(func(m android.Module) bool { return m.Name() == configuredJars.Jar(i) }, func(m android.Module) { if s, ok := m.(*SdkLibrary); ok { // TODO(208456999): instead of mapping "current" to latest, min_sdk_version should never be set to "current" if s.minSdkVersion.Specified() { if s.minSdkVersion.IsCurrent() { jar.minSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String() } else { jar.minSdkVersion = s.minSdkVersion.String() } } if s.maxSdkVersion.Specified() { if s.maxSdkVersion.IsCurrent() { jar.maxSdkVersion = ctx.Config().DefaultAppTargetSdk(ctx).String() } else { jar.maxSdkVersion = s.maxSdkVersion.String() } } } }) jars = append(jars, jar) } } return jars } func (c *ClasspathFragmentBase) generateClasspathProtoBuildActions(ctx android.ModuleContext, configuredJars android.ConfiguredJarList, jars []classpathJar) { generateProto := proptools.BoolDefault(c.properties.Generate_classpaths_proto, true) if generateProto { outputFilename := strings.ToLower(c.classpathType.String()) + ".pb" c.outputFilepath = android.PathForModuleOut(ctx, outputFilename).OutputPath c.installDirPath = android.PathForModuleInstall(ctx, "etc", "classpaths") generatedTextproto := android.PathForModuleOut(ctx, outputFilename+".textproto") writeClasspathsTextproto(ctx, generatedTextproto, jars) rule := android.NewRuleBuilder(pctx, ctx) rule.Command(). BuiltTool("conv_classpaths_proto"). Flag("encode"). Flag("--format=textproto"). FlagWithInput("--input=", generatedTextproto). FlagWithOutput("--output=", c.outputFilepath) rule.Build("classpath_fragment", "Compiling "+c.outputFilepath.String()) } classpathProtoInfo := ClasspathFragmentProtoContentInfo{ ClasspathFragmentProtoGenerated: generateProto, ClasspathFragmentProtoContents: configuredJars, ClasspathFragmentProtoInstallDir: c.installDirPath, ClasspathFragmentProtoOutput: c.outputFilepath, } android.SetProvider(ctx, ClasspathFragmentProtoContentInfoProvider, classpathProtoInfo) } func writeClasspathsTextproto(ctx android.ModuleContext, output android.WritablePath, jars []classpathJar) { var content strings.Builder for _, jar := range jars { fmt.Fprintf(&content, "jars {\n") fmt.Fprintf(&content, "path: \"%s\"\n", jar.path) fmt.Fprintf(&content, "classpath: %s\n", jar.classpath) fmt.Fprintf(&content, "min_sdk_version: \"%s\"\n", jar.minSdkVersion) fmt.Fprintf(&content, "max_sdk_version: \"%s\"\n", jar.maxSdkVersion) fmt.Fprintf(&content, "}\n") } android.WriteFileRule(ctx, output, content.String()) } // Returns AndroidMkEntries objects to install generated classpath.proto. // Do not use this to install into APEXes as the injection of the generated files happen separately for APEXes. func (c *ClasspathFragmentBase) androidMkEntries() []android.AndroidMkEntries { return []android.AndroidMkEntries{{ Class: "ETC", OutputFile: android.OptionalPathForPath(c.outputFilepath), ExtraEntries: []android.AndroidMkExtraEntriesFunc{ func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) { entries.SetString("LOCAL_MODULE_PATH", c.installDirPath.String()) entries.SetString("LOCAL_INSTALLED_MODULE_STEM", c.outputFilepath.Base()) }, }, }} } var ClasspathFragmentProtoContentInfoProvider = blueprint.NewProvider[ClasspathFragmentProtoContentInfo]() type ClasspathFragmentProtoContentInfo struct { // Whether the classpaths.proto config is generated for the fragment. ClasspathFragmentProtoGenerated bool // ClasspathFragmentProtoContents contains a list of jars that are part of this classpath fragment. ClasspathFragmentProtoContents android.ConfiguredJarList // ClasspathFragmentProtoOutput is an output path for the generated classpaths.proto config of this module. // // The file should be copied to a relevant place on device, see ClasspathFragmentProtoInstallDir // for more details. ClasspathFragmentProtoOutput android.OutputPath // ClasspathFragmentProtoInstallDir contains information about on device location for the generated classpaths.proto file. // // The path encodes expected sub-location within partitions, i.e. etc/classpaths/, // for ClasspathFragmentProtoOutput. To get sub-location, instead of the full output / make path // use android.InstallPath#Rel(). // // This is only relevant for APEX modules as they perform their own installation; while regular // system files are installed via ClasspathFragmentBase#androidMkEntries(). ClasspathFragmentProtoInstallDir android.InstallPath }