Pass custom_package to internal android_binary in android_sandboxed_sdk.

This is critical for accessing resources in sandboxed SDKs.

PiperOrigin-RevId: 643971020
Change-Id: Ibc41332ab5b54dc61244e06ba9e54cf680d851ae
diff --git a/rules/android_sandboxed_sdk/android_sandboxed_sdk_macro.bzl b/rules/android_sandboxed_sdk/android_sandboxed_sdk_macro.bzl
index 61909bf..473ce05 100644
--- a/rules/android_sandboxed_sdk/android_sandboxed_sdk_macro.bzl
+++ b/rules/android_sandboxed_sdk/android_sandboxed_sdk_macro.bzl
@@ -33,6 +33,7 @@
 visibility(PROJECT_VISIBILITY)
 
 _ATTRS = dict(
+    java_package_name = attr.string(),
     sdk_modules_config = attr.label(
         allow_single_file = [".pb.json"],
     ),
@@ -47,6 +48,15 @@
 )
 
 def _impl(ctx):
+    validation = ctx.actions.declare_file(ctx.label.name + "_validation")
+    _sandboxed_sdk_toolbox.validate_modules_config(
+        ctx,
+        output = validation,
+        sdk_module_config = ctx.file.sdk_modules_config,
+        java_package_name = ctx.attr.java_package_name,
+        sandboxed_sdk_toolbox = _get_android_toolchain(ctx).sandboxed_sdk_toolbox.files_to_run,
+        host_javabase = _common.get_host_javabase(ctx),
+    )
     sdk_api_descriptors = ctx.actions.declare_file(ctx.label.name + "_sdk_api_descriptors.jar")
     _sandboxed_sdk_toolbox.extract_api_descriptors(
         ctx,
@@ -64,6 +74,7 @@
             sdk_module_config = ctx.file.sdk_modules_config,
             sdk_api_descriptors = sdk_api_descriptors,
         ),
+        OutputGroupInfo(_validation = depset([validation])),
     ]
 
 _android_sandboxed_sdk = rule(
@@ -99,7 +110,8 @@
       visibility: A list of targets allowed to depend on this rule.
       testonly: Whether this library is only for testing.
       tags: A list of string tags passed to generated targets.
-      custom_package: Java package for resources,
+      custom_package: Java package for resources. This needs to be the same as the package set in
+        the sdk_modules_config.
       android_binary: android_binary rule used to create the intermediate SDK APK.
     """
     fully_qualified_name = "//%s:%s" % (native.package_name(), name)
@@ -130,11 +142,13 @@
         testonly = testonly,
         tags = tags,
         use_r_package = True,
+        custom_package = custom_package,
     )
 
     sdk_deploy_jar = Label("%s_deploy.jar" % bin_fqn)
     _android_sandboxed_sdk(
         name = name,
+        java_package_name = package,
         sdk_modules_config = sdk_modules_config,
         visibility = visibility,
         testonly = testonly,
diff --git a/rules/sandboxed_sdk_toolbox.bzl b/rules/sandboxed_sdk_toolbox.bzl
index 5120def..7801cce 100644
--- a/rules/sandboxed_sdk_toolbox.bzl
+++ b/rules/sandboxed_sdk_toolbox.bzl
@@ -302,6 +302,39 @@
         progress_message = "Generate SDK split properties %s" % output.short_path,
     )
 
+def _validate_modules_config(
+        ctx,
+        output = None,
+        sdk_module_config = None,
+        java_package_name = None,
+        sandboxed_sdk_toolbox = None,
+        host_javabase = None):
+    """Validates an SDK modules config file and ensures it has a valid package name.
+
+    Args:
+      ctx: The context.
+      output: The output file. It will only be created if the validation succeeds.
+      sdk_module_config: SDK Module config JSON file form an SDK bundle.
+      java_package_name: The java package name to use for the SDK.
+      sandboxed_sdk_toolbox: Toolbox executable files.
+      host_javabase: Javabase used to run the toolbox.
+    """
+    args = ctx.actions.args()
+    args.add("validate-modules-config")
+    args.add("--sdk-modules-config", sdk_module_config)
+    args.add("--java-package-name", java_package_name)
+    args.add("--output", output)
+    _java.run(
+        ctx = ctx,
+        host_javabase = host_javabase,
+        executable = sandboxed_sdk_toolbox,
+        arguments = [args],
+        inputs = [sdk_module_config],
+        outputs = [output],
+        mnemonic = "ValidateModulesConfig",
+        progress_message = "Validating SDK modules config %s" % output.short_path,
+    )
+
 sandboxed_sdk_toolbox = struct(
     extract_api_descriptors = _extract_api_descriptors,
     extract_api_descriptors_from_asar = _extract_api_descriptors_from_asar,
@@ -310,4 +343,5 @@
     generate_runtime_enabled_sdk_table = _generate_runtime_enabled_sdk_table,
     generate_sdk_dependencies_manifest = _generate_sdk_dependencies_manifest,
     generate_sdk_split_properties = _generate_sdk_split_properties,
+    validate_modules_config = _validate_modules_config,
 )
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/BUILD b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/BUILD
index 0ae13b0..2fbf67c 100644
--- a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/BUILD
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/BUILD
@@ -18,6 +18,7 @@
         "//src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/runtimeenabledsdkconfig",
         "//src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdkdependenciesmanifest",
         "//src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/sdksplitproperties",
+        "//src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig",
         "@rules_android_maven//:info_picocli_picocli",
     ],
 )
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/SandboxedSdkToolbox.java b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/SandboxedSdkToolbox.java
index ff0278a..f6447b9 100644
--- a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/SandboxedSdkToolbox.java
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/SandboxedSdkToolbox.java
@@ -22,6 +22,7 @@
 import com.google.devtools.build.android.sandboxedsdktoolbox.runtimeenabledsdkconfig.GenerateRuntimeEnabledSdkTableCommand;
 import com.google.devtools.build.android.sandboxedsdktoolbox.sdkdependenciesmanifest.GenerateSdkDependenciesManifestCommand;
 import com.google.devtools.build.android.sandboxedsdktoolbox.sdksplitproperties.GenerateSdkSplitPropertiesCommand;
+import com.google.devtools.build.android.sandboxedsdktoolbox.validatemodulesconfig.ValidateModulesConfigCommand;
 import picocli.CommandLine;
 import picocli.CommandLine.Command;
 
@@ -36,6 +37,7 @@
       GenerateRuntimeEnabledSdkTableCommand.class,
       GenerateSdkDependenciesManifestCommand.class,
       GenerateSdkSplitPropertiesCommand.class,
+      ValidateModulesConfigCommand.class,
     })
 public final class SandboxedSdkToolbox {
 
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/BUILD b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/BUILD
new file mode 100644
index 0000000..9e013f4
--- /dev/null
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/BUILD
@@ -0,0 +1,17 @@
+# Command to validate SDK modules config.
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//:__subpackages__"],
+)
+
+licenses(["notice"])
+
+java_library(
+    name = "validatemodulesconfig",
+    srcs = glob(["*.java"]),
+    deps = [
+        "//src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/info",
+        "@rules_android_maven//:info_picocli_picocli",
+    ],
+)
diff --git a/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/ValidateModulesConfigCommand.java b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/ValidateModulesConfigCommand.java
new file mode 100644
index 0000000..f12dc88
--- /dev/null
+++ b/src/tools/java/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/ValidateModulesConfigCommand.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright 2024 The Bazel Authors. All rights reserved.
+ *
+ * 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 com.google.devtools.build.android.sandboxedsdktoolbox.validatemodulesconfig;
+
+import com.google.devtools.build.android.sandboxedsdktoolbox.info.SdkInfo;
+import com.google.devtools.build.android.sandboxedsdktoolbox.info.SdkInfoReader;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import picocli.CommandLine.Command;
+import picocli.CommandLine.Option;
+
+/** Checks if the given modules config is valid and compatible with other build parameters. */
+@Command(
+    name = "validate-modules-config",
+    description =
+        "Checks if the given modules config is valid and compatible with other build"
+            + " parameters.")
+public final class ValidateModulesConfigCommand implements Runnable {
+
+  @Option(names = "--sdk-modules-config", required = true)
+  Path sdkModuleConfigPath;
+
+  @Option(names = "--java-package-name", required = true)
+  String javaPackageName;
+
+  @Option(names = "--output", required = true)
+  Path output;
+
+  @Override
+  public void run() {
+    SdkInfo info = SdkInfoReader.readFromSdkModuleJsonFile(sdkModuleConfigPath);
+
+    if (!info.getPackageName().equals(javaPackageName)) {
+      throw new IllegalArgumentException(
+          String.format(
+              "The package name in the modules config (%s) does not match the java package name "
+                  + "(%s). This causes runtime errors when running the SDK as a split APK.",
+              info.getPackageName(), javaPackageName));
+    }
+
+    try {
+      // Empty file representing a successful validation.
+      Files.createFile(output);
+    } catch (IOException e) {
+      throw new IllegalStateException("Failed to create output file", e);
+    }
+  }
+
+  private ValidateModulesConfigCommand() {}
+}
diff --git a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/BUILD b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/BUILD
new file mode 100644
index 0000000..38470ea
--- /dev/null
+++ b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/BUILD
@@ -0,0 +1,22 @@
+# Tests for validate-modules-config command.
+
+package(
+    default_applicable_licenses = ["//:license"],
+    default_visibility = ["//:__subpackages__"],
+)
+
+licenses(["notice"])
+
+java_test(
+    name = "ValidateModulesConfigCommandTest",
+    size = "small",
+    srcs = ["ValidateModulesConfigCommandTest.java"],
+    data = glob(["testdata/*"]),
+    deps = [
+        "//src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/utils",
+        "@rules_android_maven//:com_android_tools_build_bundletool",
+        "@rules_android_maven//:com_google_protobuf_protobuf_java_util",
+        "@rules_android_maven//:com_google_truth_truth",
+        "@rules_android_maven//:junit_junit",
+    ],
+)
diff --git a/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/ValidateModulesConfigCommandTest.java b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/ValidateModulesConfigCommandTest.java
new file mode 100644
index 0000000..4d5deea
--- /dev/null
+++ b/src/tools/javatests/com/google/devtools/build/android/sandboxedsdktoolbox/validatemodulesconfig/ValidateModulesConfigCommandTest.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2024 The Bazel Authors. All rights reserved.
+ *
+ * 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 com.google.devtools.build.android.sandboxedsdktoolbox.validatemodulesconfig;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.devtools.build.android.sandboxedsdktoolbox.utils.Runner.runCommand;
+
+import com.android.bundle.SdkModulesConfigOuterClass.SdkModulesConfig;
+import com.google.devtools.build.android.sandboxedsdktoolbox.utils.CommandResult;
+import com.google.protobuf.util.JsonFormat;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class ValidateModulesConfigCommandTest {
+
+  @Rule public final TemporaryFolder testFolder = new TemporaryFolder();
+
+  @Test
+  public void modulesConfig_withTheSameJavaPackage_succeeds() throws Exception {
+    SdkModulesConfig config =
+        SdkModulesConfig.newBuilder().setSdkPackageName("com.example.package").build();
+    String javaPackageName = "com.example.package";
+    Path output = testFolder.getRoot().toPath().resolve("output");
+
+    CommandResult result = runValidateCommand(config, javaPackageName, output);
+
+    assertThat(result.getOutput()).isEmpty();
+    assertThat(result.getStatusCode()).isEqualTo(0);
+    assertThat(Files.exists(output)).isTrue();
+  }
+
+  @Test
+  public void modulesConfig_withMismatchedJavaPackage_failsValidation() throws Exception {
+    SdkModulesConfig config =
+        SdkModulesConfig.newBuilder().setSdkPackageName("com.example.package").build();
+    String javaPackageName = "com.example.package.different";
+    Path output = testFolder.getRoot().toPath().resolve("output");
+
+    CommandResult result = runValidateCommand(config, javaPackageName, output);
+
+    assertThat(result.getOutput())
+        .contains(
+            "The package name in the modules config (com.example.package) does not match the java"
+                + " package name (com.example.package.different)");
+    assertThat(result.getStatusCode()).isEqualTo(1);
+    assertThat(Files.exists(output)).isFalse();
+  }
+
+  private CommandResult runValidateCommand(
+      SdkModulesConfig config, String javaPackageName, Path output) throws Exception {
+    Path sdkModulesConfigPath = testFolder.getRoot().toPath().resolve("sdk-modules-config.pb.json");
+    Files.writeString(sdkModulesConfigPath, JsonFormat.printer().print(config));
+    return runCommand(
+        "validate-modules-config",
+        "--sdk-modules-config",
+        sdkModulesConfigPath.toString(),
+        "--java-package-name",
+        javaPackageName,
+        "--output",
+        output.toString());
+  }
+}