diff --git a/android/bazel_handler.go b/android/bazel_handler.go index 5e8a1839e..5804a46ed 100644 --- a/android/bazel_handler.go +++ b/android/bazel_handler.go @@ -91,6 +91,10 @@ type configKey struct { osType OsType } +func (c configKey) String() string { + return fmt.Sprintf("%s::%s", c.arch, c.osType) +} + // Map key to describe bazel cquery requests. type cqueryKey struct { label string @@ -98,6 +102,11 @@ type cqueryKey struct { configKey configKey } +func (c cqueryKey) String() string { + return fmt.Sprintf("cquery(%s,%s,%s)", c.label, c.requestType.Name(), c.configKey) + +} + // BazelContext is a context object useful for interacting with Bazel during // the course of a build. Use of Bazel to evaluate part of the build graph // is referred to as a "mixed build". (Some modules are managed by Soong, @@ -123,6 +132,9 @@ type BazelContext interface { // TODO(b/232976601): Remove. GetPythonBinary(label string, cfgKey configKey) (string, error) + // Returns the results of the GetApexInfo query (including output files) + GetApexInfo(label string, cfgkey configKey) (cquery.ApexCqueryInfo, error) + // ** end Cquery Results Retrieval Functions // Issues commands to Bazel to receive results for all cquery requests @@ -186,6 +198,7 @@ type MockBazelContext struct { LabelToOutputFiles map[string][]string LabelToCcInfo map[string]cquery.CcInfo LabelToPythonBinary map[string]string + LabelToApexInfo map[string]cquery.ApexCqueryInfo } func (m MockBazelContext) QueueBazelRequest(_ string, _ cqueryRequest, _ configKey) { @@ -207,6 +220,10 @@ func (m MockBazelContext) GetPythonBinary(label string, _ configKey) (string, er return result, nil } +func (n MockBazelContext) GetApexInfo(_ string, _ configKey) (cquery.ApexCqueryInfo, error) { + panic("unimplemented") +} + func (m MockBazelContext) InvokeBazel(_ Config) error { panic("unimplemented") } @@ -261,6 +278,14 @@ func (bazelCtx *bazelContext) GetPythonBinary(label string, cfgKey configKey) (s return "", fmt.Errorf("no bazel response found for %v", key) } +func (bazelCtx *bazelContext) GetApexInfo(label string, cfgKey configKey) (cquery.ApexCqueryInfo, error) { + key := cqueryKey{label, cquery.GetApexInfo, cfgKey} + if rawString, ok := bazelCtx.results[key]; ok { + return cquery.GetApexInfo.ParseResult(strings.TrimSpace(rawString)), nil + } + return cquery.ApexCqueryInfo{}, fmt.Errorf("no bazel response found for %v", key) +} + func (n noopBazelContext) QueueBazelRequest(_ string, _ cqueryRequest, _ configKey) { panic("unimplemented") } @@ -277,6 +302,10 @@ func (n noopBazelContext) GetPythonBinary(_ string, _ configKey) (string, error) panic("unimplemented") } +func (n noopBazelContext) GetApexInfo(_ string, _ configKey) (cquery.ApexCqueryInfo, error) { + panic("unimplemented") +} + func (n noopBazelContext) InvokeBazel(_ Config) error { panic("unimplemented") } @@ -401,11 +430,9 @@ func (r *builtinBazelRunner) issueBazelCommand(paths *bazelPaths, runName bazel. cmdFlags := []string{ "--output_base=" + absolutePath(paths.outputBase), command.command, - } - cmdFlags = append(cmdFlags, command.expression) - cmdFlags = append(cmdFlags, + command.expression, // TODO(asmundak): is it needed in every build? - "--profile="+shared.BazelMetricsFilename(paths, runName), + "--profile=" + shared.BazelMetricsFilename(paths, runName), // Set default platforms to canonicalized values for mixed builds requests. // If these are set in the bazelrc, they will have values that are @@ -426,21 +453,23 @@ func (r *builtinBazelRunner) issueBazelCommand(paths *bazelPaths, runName bazel. // Suppress noise "--ui_event_filters=-INFO", - "--noshow_progress") + "--noshow_progress"} cmdFlags = append(cmdFlags, extraFlags...) bazelCmd := exec.Command(paths.bazelPath, cmdFlags...) bazelCmd.Dir = absolutePath(paths.syntheticWorkspaceDir()) - bazelCmd.Env = append(os.Environ(), - "HOME="+paths.homeDir, + extraEnv := []string{ + "HOME=" + paths.homeDir, pwdPrefix(), - "BUILD_DIR="+absolutePath(paths.soongOutDir), + "BUILD_DIR=" + absolutePath(paths.soongOutDir), // Make OUT_DIR absolute here so tools/bazel.sh uses the correct // OUT_DIR at /out, instead of /out/soong/workspace/out. - "OUT_DIR="+absolutePath(paths.outDir()), + "OUT_DIR=" + absolutePath(paths.outDir()), // Disables local host detection of gcc; toolchain information is defined // explicitly in BUILD files. - "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1") + "BAZEL_DO_NOT_DETECT_CPP_TOOLCHAIN=1", + } + bazelCmd.Env = append(os.Environ(), extraEnv...) stderr := &bytes.Buffer{} bazelCmd.Stderr = stderr @@ -651,6 +680,15 @@ def get_arch(target): fail("expected platform name of the form 'android_' or 'linux_', but was " + str(platforms)) return "UNKNOWN" +def json_for_file(key, file): + return '"' + key + '":"' + file.path + '"' + +def json_for_files(key, files): + return '"' + key + '":[' + ",".join(['"' + f.path + '"' for f in files]) + ']' + +def json_for_labels(key, ll): + return '"' + key + '":[' + ",".join(['"' + str(x) + '"' for x in ll]) + ']' + def format(target): id_string = str(target.label) + "|" + get_arch(target) @@ -728,7 +766,7 @@ func (context *bazelContext) InvokeBazel(config Config) error { cqueryOutput, cqueryErr, err := context.issueBazelCommand(context.paths, bazel.CqueryBuildRootRunName, cqueryCmd, "--output=starlark", "--starlark:file="+absolutePath(cqueryFileRelpath)) if err != nil { - err = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryOutput), 0666) + _ = ioutil.WriteFile(filepath.Join(soongInjectionPath, "cquery.out"), []byte(cqueryOutput), 0666) } if err != nil { return err diff --git a/android/config.go b/android/config.go index a5337d07d..8c7d789c8 100644 --- a/android/config.go +++ b/android/config.go @@ -1446,8 +1446,8 @@ func (c *config) ForceApexSymlinkOptimization() bool { return Bool(c.productVariables.ForceApexSymlinkOptimization) } -func (c *config) CompressedApex() bool { - return Bool(c.productVariables.CompressedApex) +func (c *config) ApexCompressionEnabled() bool { + return Bool(c.productVariables.CompressedApex) && !c.UnbundledBuildApps() } func (c *config) EnforceSystemCertificate() bool { diff --git a/apex/apex.go b/apex/apex.go index d1462d0af..155b0b4e3 100644 --- a/apex/apex.go +++ b/apex/apex.go @@ -17,6 +17,7 @@ package apex import ( + "android/soong/bazel/cquery" "fmt" "path/filepath" "regexp" @@ -1803,6 +1804,184 @@ func (f fsType) string() string { } } +var _ android.MixedBuildBuildable = (*apexBundle)(nil) + +func (a *apexBundle) IsMixedBuildSupported(ctx android.BaseModuleContext) bool { + return ctx.ModuleType() == "apex" && a.properties.ApexType == imageApex +} + +func (a *apexBundle) QueueBazelCall(ctx android.BaseModuleContext) { + bazelCtx := ctx.Config().BazelContext + bazelCtx.QueueBazelRequest(a.GetBazelLabel(ctx, a), cquery.GetApexInfo, android.GetConfigKey(ctx)) +} + +func (a *apexBundle) ProcessBazelQueryResponse(ctx android.ModuleContext) { + if !a.commonBuildActions(ctx) { + return + } + + a.setApexTypeAndSuffix(ctx) + a.setPayloadFsType(ctx) + a.setSystemLibLink(ctx) + + if a.properties.ApexType != zipApex { + a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx, a.primaryApexType) + } + + bazelCtx := ctx.Config().BazelContext + outputs, err := bazelCtx.GetApexInfo(a.GetBazelLabel(ctx, a), android.GetConfigKey(ctx)) + if err != nil { + ctx.ModuleErrorf(err.Error()) + return + } + a.installDir = android.PathForModuleInstall(ctx, "apex") + a.outputApexFile = android.PathForBazelOut(ctx, outputs.SignedOutput) + a.outputFile = a.outputApexFile + a.setCompression(ctx) + + a.publicKeyFile = android.PathForBazelOut(ctx, outputs.BundleKeyPair[0]) + a.privateKeyFile = android.PathForBazelOut(ctx, outputs.BundleKeyPair[1]) + a.containerCertificateFile = android.PathForBazelOut(ctx, outputs.ContainerKeyPair[0]) + a.containerPrivateKeyFile = android.PathForBazelOut(ctx, outputs.ContainerKeyPair[1]) + apexType := a.properties.ApexType + switch apexType { + case imageApex: + // TODO(asmundak): Bazel does not create these files yet. + // b/190817312 + a.htmlGzNotice = android.PathForBazelOut(ctx, "NOTICE.html.gz") + // b/239081457 + a.bundleModuleFile = android.PathForBazelOut(ctx, a.Name()+apexType.suffix()+"-base.zip") + // b/239081455 + a.nativeApisUsedByModuleFile = android.ModuleOutPath(android.PathForBazelOut(ctx, a.Name()+"_using.txt")) + // b/239081456 + a.nativeApisBackedByModuleFile = android.ModuleOutPath(android.PathForBazelOut(ctx, a.Name()+"_backing.txt")) + // b/239084755 + a.javaApisUsedByModuleFile = android.ModuleOutPath(android.PathForBazelOut(ctx, a.Name()+"_using.xml")) + installSuffix := imageApexSuffix + if a.isCompressed { + installSuffix = imageCapexSuffix + } + a.installedFile = ctx.InstallFile(a.installDir, a.Name()+installSuffix, a.outputFile, + a.compatSymlinks.Paths()...) + default: + panic(fmt.Errorf("unexpected apex_type for the ProcessBazelQuery: %v", a.properties.ApexType)) + } + + /* + TODO(asmundak): compared to building an APEX with Soong, building it with Bazel does not + return filesInfo and requiredDeps fields (in the Soong build the latter is updated). + Fix this, as these fields are subsequently used in apex/androidmk.go and in apex/builder/go + To find out what Soong build puts there, run: + vctx := visitorContext{handleSpecialLibs: !android.Bool(a.properties.Ignore_system_library_special_case)} + ctx.WalkDepsBlueprint(func(child, parent blueprint.Module) bool { + return a.depVisitor(&vctx, ctx, child, parent) + }) + vctx.normalizeFileInfo() + */ + +} + +func (a *apexBundle) setCompression(ctx android.ModuleContext) { + if a.properties.ApexType != imageApex { + a.isCompressed = false + } else if a.testOnlyShouldForceCompression() { + a.isCompressed = true + } else { + a.isCompressed = ctx.Config().ApexCompressionEnabled() && a.isCompressable() + } +} + +func (a *apexBundle) setSystemLibLink(ctx android.ModuleContext) { + // Optimization. If we are building bundled APEX, for the files that are gathered due to the + // transitive dependencies, don't place them inside the APEX, but place a symlink pointing + // the same library in the system partition, thus effectively sharing the same libraries + // across the APEX boundary. For unbundled APEX, all the gathered files are actually placed + // in the APEX. + a.linkToSystemLib = !ctx.Config().UnbundledBuild() && a.installable() + + // APEXes targeting other than system/system_ext partitions use vendor/product variants. + // So we can't link them to /system/lib libs which are core variants. + if a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) { + a.linkToSystemLib = false + } + + forced := ctx.Config().ForceApexSymlinkOptimization() + updatable := a.Updatable() || a.FutureUpdatable() + + // We don't need the optimization for updatable APEXes, as it might give false signal + // to the system health when the APEXes are still bundled (b/149805758). + if !forced && updatable && a.properties.ApexType == imageApex { + a.linkToSystemLib = false + } + + // We also don't want the optimization for host APEXes, because it doesn't make sense. + if ctx.Host() { + a.linkToSystemLib = false + } +} + +func (a *apexBundle) setPayloadFsType(ctx android.ModuleContext) { + switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) { + case ext4FsType: + a.payloadFsType = ext4 + case f2fsFsType: + a.payloadFsType = f2fs + case erofsFsType: + a.payloadFsType = erofs + default: + ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs, erofs]", *a.properties.Payload_fs_type) + } +} + +func (a *apexBundle) setApexTypeAndSuffix(ctx android.ModuleContext) { + // Set suffix and primaryApexType depending on the ApexType + buildFlattenedAsDefault := ctx.Config().FlattenApex() + switch a.properties.ApexType { + case imageApex: + if buildFlattenedAsDefault { + a.suffix = imageApexSuffix + } else { + a.suffix = "" + a.primaryApexType = true + + if ctx.Config().InstallExtraFlattenedApexes() { + a.requiredDeps = append(a.requiredDeps, a.Name()+flattenedSuffix) + } + } + case zipApex: + if proptools.String(a.properties.Payload_type) == "zip" { + a.suffix = "" + a.primaryApexType = true + } else { + a.suffix = zipApexSuffix + } + case flattenedApex: + if buildFlattenedAsDefault { + a.suffix = "" + a.primaryApexType = true + } else { + a.suffix = flattenedSuffix + } + } +} + +func (a apexBundle) isCompressable() bool { + return proptools.BoolDefault(a.overridableProperties.Compressible, false) && !a.testApex +} + +func (a *apexBundle) commonBuildActions(ctx android.ModuleContext) bool { + a.checkApexAvailability(ctx) + a.checkUpdatable(ctx) + a.CheckMinSdkVersion(ctx) + a.checkStaticLinkingToStubLibraries(ctx) + a.checkStaticExecutables(ctx) + if len(a.properties.Tests) > 0 && !a.testApex { + ctx.PropertyErrorf("tests", "property allowed only in apex_test module type") + return false + } + return true +} + type visitorContext struct { // all the files that will be included in this APEX filesInfo []apexFile @@ -2188,16 +2367,9 @@ func (a *apexBundle) depVisitor(vctx *visitorContext, ctx android.ModuleContext, func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) { //////////////////////////////////////////////////////////////////////////////////////////// // 1) do some validity checks such as apex_available, min_sdk_version, etc. - a.checkApexAvailability(ctx) - a.checkUpdatable(ctx) - a.CheckMinSdkVersion(ctx) - a.checkStaticLinkingToStubLibraries(ctx) - a.checkStaticExecutables(ctx) - if len(a.properties.Tests) > 0 && !a.testApex { - ctx.PropertyErrorf("tests", "property allowed only in apex_test module type") + if !a.commonBuildActions(ctx) { return } - //////////////////////////////////////////////////////////////////////////////////////////// // 2) traverse the dependency tree to collect apexFile structs from them. @@ -2219,74 +2391,9 @@ func (a *apexBundle) GenerateAndroidBuildActions(ctx android.ModuleContext) { a.installDir = android.PathForModuleInstall(ctx, "apex") a.filesInfo = vctx.filesInfo - // Set suffix and primaryApexType depending on the ApexType - buildFlattenedAsDefault := ctx.Config().FlattenApex() - switch a.properties.ApexType { - case imageApex: - if buildFlattenedAsDefault { - a.suffix = imageApexSuffix - } else { - a.suffix = "" - a.primaryApexType = true - - if ctx.Config().InstallExtraFlattenedApexes() { - a.requiredDeps = append(a.requiredDeps, a.Name()+flattenedSuffix) - } - } - case zipApex: - if proptools.String(a.properties.Payload_type) == "zip" { - a.suffix = "" - a.primaryApexType = true - } else { - a.suffix = zipApexSuffix - } - case flattenedApex: - if buildFlattenedAsDefault { - a.suffix = "" - a.primaryApexType = true - } else { - a.suffix = flattenedSuffix - } - } - - switch proptools.StringDefault(a.properties.Payload_fs_type, ext4FsType) { - case ext4FsType: - a.payloadFsType = ext4 - case f2fsFsType: - a.payloadFsType = f2fs - case erofsFsType: - a.payloadFsType = erofs - default: - ctx.PropertyErrorf("payload_fs_type", "%q is not a valid filesystem for apex [ext4, f2fs, erofs]", *a.properties.Payload_fs_type) - } - - // Optimization. If we are building bundled APEX, for the files that are gathered due to the - // transitive dependencies, don't place them inside the APEX, but place a symlink pointing - // the same library in the system partition, thus effectively sharing the same libraries - // across the APEX boundary. For unbundled APEX, all the gathered files are actually placed - // in the APEX. - a.linkToSystemLib = !ctx.Config().UnbundledBuild() && a.installable() - - // APEXes targeting other than system/system_ext partitions use vendor/product variants. - // So we can't link them to /system/lib libs which are core variants. - if a.SocSpecific() || a.DeviceSpecific() || (a.ProductSpecific() && ctx.Config().EnforceProductPartitionInterface()) { - a.linkToSystemLib = false - } - - forced := ctx.Config().ForceApexSymlinkOptimization() - updatable := a.Updatable() || a.FutureUpdatable() - - // We don't need the optimization for updatable APEXes, as it might give false signal - // to the system health when the APEXes are still bundled (b/149805758). - if !forced && updatable && a.properties.ApexType == imageApex { - a.linkToSystemLib = false - } - - // We also don't want the optimization for host APEXes, because it doesn't make sense. - if ctx.Host() { - a.linkToSystemLib = false - } - + a.setApexTypeAndSuffix(ctx) + a.setPayloadFsType(ctx) + a.setSystemLibLink(ctx) if a.properties.ApexType != zipApex { a.compatSymlinks = makeCompatSymlinks(a.BaseModuleName(), ctx, a.primaryApexType) } diff --git a/apex/builder.go b/apex/builder.go index 1956b4447..58acf71d6 100644 --- a/apex/builder.go +++ b/apex/builder.go @@ -549,8 +549,6 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { outHostBinDir := ctx.Config().HostToolPath(ctx, "").String() prebuiltSdkToolsBinDir := filepath.Join("prebuilts", "sdk", "tools", runtime.GOOS, "bin") - // Figure out if we need to compress the apex. - compressionEnabled := ctx.Config().CompressedApex() && proptools.BoolDefault(a.overridableProperties.Compressible, false) && !a.testApex && !ctx.Config().UnbundledBuildApps() if apexType == imageApex { //////////////////////////////////////////////////////////////////////////////////// @@ -635,10 +633,15 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { implicitInputs = append(implicitInputs, noticeAssetPath) optFlags = append(optFlags, "--assets_dir "+filepath.Dir(noticeAssetPath.String())) - if (moduleMinSdkVersion.GreaterThan(android.SdkVersion_Android10) && !a.shouldGenerateHashtree()) && !compressionEnabled { - // Apexes which are supposed to be installed in builtin dirs(/system, etc) - // don't need hashtree for activation. Therefore, by removing hashtree from - // apex bundle (filesystem image in it, to be specific), we can save storage. + // Apexes which are supposed to be installed in builtin dirs(/system, etc) + // don't need hashtree for activation. Therefore, by removing hashtree from + // apex bundle (filesystem image in it, to be specific), we can save storage. + needHashTree := moduleMinSdkVersion.LessThanOrEqualTo(android.SdkVersion_Android10) || + a.shouldGenerateHashtree() + if ctx.Config().ApexCompressionEnabled() && a.isCompressable() { + needHashTree = true + } + if !needHashTree { optFlags = append(optFlags, "--no_hashtree") } @@ -806,8 +809,9 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { return } - if apexType == imageApex && (compressionEnabled || a.testOnlyShouldForceCompression()) { - a.isCompressed = true + installSuffix := suffix + a.setCompression(ctx) + if a.isCompressed { unsignedCompressedOutputFile := android.PathForModuleOut(ctx, a.Name()+imageCapexSuffix+".unsigned") compressRule := android.NewRuleBuilder(pctx, ctx) @@ -835,10 +839,6 @@ func (a *apexBundle) buildUnflattenedApex(ctx android.ModuleContext) { Args: args, }) a.outputFile = signedCompressedOutputFile - } - - installSuffix := suffix - if a.isCompressed { installSuffix = imageCapexSuffix } diff --git a/bazel/aquery.go b/bazel/aquery.go index ae2b107e1..418b14321 100644 --- a/bazel/aquery.go +++ b/bazel/aquery.go @@ -574,7 +574,7 @@ func (a *aqueryArtifactHandler) getOutputPaths(actionEntry action) (outputPaths // expandTemplateContent substitutes the tokens in a template. func expandTemplateContent(actionEntry action) string { - replacerString := []string{} + var replacerString []string for _, pair := range actionEntry.Substitutions { value := pair.Value if val, ok := templateActionOverriddenTokens[pair.Key]; ok { @@ -647,7 +647,7 @@ func expandPathFragment(id pathFragmentId, pathFragmentsMap map[pathFragmentId]p } labels = append([]string{currFragment.Label}, labels...) if currId == currFragment.ParentId { - return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment) + return "", fmt.Errorf("fragment cannot refer to itself as parent %#v", currFragment) } currId = currFragment.ParentId } diff --git a/bazel/cquery/request_type.go b/bazel/cquery/request_type.go index f5435f25f..d32e61969 100644 --- a/bazel/cquery/request_type.go +++ b/bazel/cquery/request_type.go @@ -1,6 +1,7 @@ package cquery import ( + "encoding/json" "fmt" "strings" ) @@ -9,6 +10,7 @@ var ( GetOutputFiles = &getOutputFilesRequestType{} GetPythonBinary = &getPythonBinaryRequestType{} GetCcInfo = &getCcInfoType{} + GetApexInfo = &getApexInfoType{} ) type CcInfo struct { @@ -179,7 +181,7 @@ func (g getCcInfoType) ParseResult(rawString string) (CcInfo, error) { const expectedLen = 10 splitString := strings.Split(rawString, "|") if len(splitString) != expectedLen { - return CcInfo{}, fmt.Errorf("Expected %d items, got %q", expectedLen, splitString) + return CcInfo{}, fmt.Errorf("expected %d items, got %q", expectedLen, splitString) } outputFilesString := splitString[0] ccObjectsString := splitString[1] @@ -215,6 +217,54 @@ func (g getCcInfoType) ParseResult(rawString string) (CcInfo, error) { }, nil } +// Query Bazel for the artifacts generated by the apex modules. +type getApexInfoType struct{} + +// Name returns a string name for this request type. Such request type names must be unique, +// and must only consist of alphanumeric characters. +func (g getApexInfoType) Name() string { + return "getApexInfo" +} + +// StarlarkFunctionBody returns a starlark function body to process this request type. +// The returned string is the body of a Starlark function which obtains +// all request-relevant information about a target and returns a string containing +// this information. The function should have the following properties: +// - `target` is the only parameter to this function (a configured target). +// - The return value must be a string. +// - The function body should not be indented outside of its own scope. +func (g getApexInfoType) StarlarkFunctionBody() string { + return `info = providers(target)["//build/bazel/rules/apex:apex.bzl%ApexInfo"] +return "{%s}" % ",".join([ + json_for_file("signed_output", info.signed_output), + json_for_file("unsigned_output", info.unsigned_output), + json_for_labels("provides_native_libs", info.provides_native_libs), + json_for_labels("requires_native_libs", info.requires_native_libs), + json_for_files("bundle_key_pair", info.bundle_key_pair), + json_for_files("container_key_pair", info.container_key_pair) + ])` +} + +type ApexCqueryInfo struct { + SignedOutput string `json:"signed_output"` + UnsignedOutput string `json:"unsigned_output"` + ProvidesLibs []string `json:"provides_native_libs"` + RequiresLibs []string `json:"requires_native_libs"` + BundleKeyPair []string `json:"bundle_key_pair"` + ContainerKeyPair []string `json:"container_key_pair"` +} + +// ParseResult returns a value obtained by parsing the result of the request's Starlark function. +// The given rawString must correspond to the string output which was created by evaluating the +// Starlark given in StarlarkFunctionBody. +func (g getApexInfoType) ParseResult(rawString string) ApexCqueryInfo { + var info ApexCqueryInfo + if err := json.Unmarshal([]byte(rawString), &info); err != nil { + panic(fmt.Errorf("cannot parse cquery result '%s': %s", rawString, err)) + } + return info +} + // splitOrEmpty is a modification of strings.Split() that returns an empty list // if the given string is empty. func splitOrEmpty(s string, sep string) []string { diff --git a/bazel/cquery/request_type_test.go b/bazel/cquery/request_type_test.go index 606e285bb..34248ce81 100644 --- a/bazel/cquery/request_type_test.go +++ b/bazel/cquery/request_type_test.go @@ -148,13 +148,13 @@ func TestGetCcInfoParseResults(t *testing.T) { description: "too few result splits", input: "|", expectedOutput: CcInfo{}, - expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", expectedSplits, []string{"", ""}), + expectedErrorMessage: fmt.Sprintf("expected %d items, got %q", expectedSplits, []string{"", ""}), }, { description: "too many result splits", input: strings.Repeat("|", expectedSplits+1), // 2 too many expectedOutput: CcInfo{}, - expectedErrorMessage: fmt.Sprintf("Expected %d items, got %q", expectedSplits, make([]string, expectedSplits+2)), + expectedErrorMessage: fmt.Sprintf("expected %d items, got %q", expectedSplits, make([]string, expectedSplits+2)), }, } for _, tc := range testCases { @@ -167,3 +167,40 @@ func TestGetCcInfoParseResults(t *testing.T) { } } } + +func TestGetApexInfoParseResults(t *testing.T) { + testCases := []struct { + description string + input string + expectedOutput ApexCqueryInfo + }{ + { + description: "no result", + input: "{}", + expectedOutput: ApexCqueryInfo{}, + }, + { + description: "one result", + input: `{"signed_output":"my.apex",` + + `"unsigned_output":"my.apex.unsigned",` + + `"requires_native_libs":["//bionic/libc:libc","//bionic/libdl:libdl"],` + + `"bundle_key_pair":["foo.pem","foo.privkey"],` + + `"container_key_pair":["foo.x509.pem", "foo.pk8"],` + + `"provides_native_libs":[]}`, + expectedOutput: ApexCqueryInfo{ + SignedOutput: "my.apex", + UnsignedOutput: "my.apex.unsigned", + RequiresLibs: []string{"//bionic/libc:libc", "//bionic/libdl:libdl"}, + ProvidesLibs: []string{}, + BundleKeyPair: []string{"foo.pem", "foo.privkey"}, + ContainerKeyPair: []string{"foo.x509.pem", "foo.pk8"}, + }, + }, + } + for _, tc := range testCases { + actualOutput := GetApexInfo.ParseResult(tc.input) + if !reflect.DeepEqual(tc.expectedOutput, actualOutput) { + t.Errorf("%q: expected %#v != actual %#v", tc.description, tc.expectedOutput, actualOutput) + } + } +}