Merge "Add mdns_aidl_interface libs to allowed_deps."
diff --git a/build/mainline_modules_sdks.py b/build/mainline_modules_sdks.py
index ed0f14a..3fe0ddd 100755
--- a/build/mainline_modules_sdks.py
+++ b/build/mainline_modules_sdks.py
@@ -13,7 +13,6 @@
 # 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.
-
 """Builds SDK snapshots.
 
 If the environment variable TARGET_BUILD_APPS is nonempty then only the SDKs for
@@ -175,6 +174,88 @@
         file.write("\n".join(header_lines + content_lines) + "\n")
 
 
+@dataclasses.dataclass()
+class SubprocessRunner:
+    """Runs subprocesses"""
+
+    # Destination for stdout from subprocesses.
+    #
+    # This (and the following stderr) are needed to allow the tests to be run
+    # in Intellij. This ensures that the tests are run with stdout/stderr
+    # objects that work when passed to subprocess.run(stdout/stderr). Without it
+    # the tests are run with a FlushingStringIO object that has no fileno
+    # attribute - https://youtrack.jetbrains.com/issue/PY-27883.
+    stdout: io.TextIOBase = sys.stdout
+
+    # Destination for stderr from subprocesses.
+    stderr: io.TextIOBase = sys.stderr
+
+    def run(self, *args, **kwargs):
+        return subprocess.run(
+            *args, check=True, stdout=self.stdout, stderr=self.stderr, **kwargs)
+
+
+@dataclasses.dataclass()
+class SnapshotBuilder:
+    """Builds sdk snapshots"""
+
+    # Used to run subprocesses for building snapshots.
+    subprocess_runner: SubprocessRunner
+
+    # The OUT_DIR environment variable.
+    out_dir: str
+
+    def get_mainline_sdks_path(self):
+        """Get the path to the Soong mainline-sdks directory"""
+        return os.path.join(self.out_dir, "soong/mainline-sdks")
+
+    def get_sdk_path(self, sdk_name, sdk_version):
+        """Get the path to the sdk snapshot zip file produced by soong"""
+        return os.path.join(self.get_mainline_sdks_path(),
+                            f"{sdk_name}-{sdk_version}.zip")
+
+    def build_snapshots(self, sdk_versions, modules):
+        # Build the SDKs once for each version.
+        for sdk_version in sdk_versions:
+            # Compute the paths to all the Soong generated sdk snapshot files
+            # required by this script.
+            paths = [
+                self.get_sdk_path(sdk, sdk_version)
+                for module in modules
+                for sdk in module.sdks
+            ]
+
+            # TODO(ngeoffray): remove SOONG_ALLOW_MISSING_DEPENDENCIES, but we
+            # currently break without it.
+            #
+            # Set SOONG_SDK_SNAPSHOT_USE_SRCJAR to generate .srcjars inside sdk
+            # zip files as expected by prebuilt drop.
+            extraEnv = {
+                "SOONG_ALLOW_MISSING_DEPENDENCIES": "true",
+                "SOONG_SDK_SNAPSHOT_USE_SRCJAR": "true",
+                "SOONG_SDK_SNAPSHOT_VERSION": sdk_version,
+            }
+            # Unless explicitly specified in the calling environment set
+            # TARGET_BUILD_VARIANT=user.
+            # This MUST be identical to the TARGET_BUILD_VARIANT used to build
+            # the corresponding APEXes otherwise it could result in different
+            # hidden API flags, see http://b/202398851#comment29 for more info.
+            targetBuildVariant = os.environ.get("TARGET_BUILD_VARIANT", "user")
+            cmd = [
+                "build/soong/soong_ui.bash",
+                "--make-mode",
+                "--soong-only",
+                f"TARGET_BUILD_VARIANT={targetBuildVariant}",
+                "TARGET_PRODUCT=mainline_sdk",
+                "MODULE_BUILD_FROM_SOURCE=true",
+                "out/soong/apex/depsinfo/new-allowed-deps.txt.check",
+            ] + paths
+            print_command(extraEnv, cmd)
+            env = os.environ.copy()
+            env.update(extraEnv)
+            self.subprocess_runner.run(cmd, env=env)
+
+
 @dataclasses.dataclass(frozen=True)
 class MainlineModule:
     """Represents a mainline module"""
@@ -267,9 +348,6 @@
     ),
 ]
 
-# Only used by the test.
-MAINLINE_MODULES_BY_APEX = dict((m.apex, m) for m in MAINLINE_MODULES)
-
 # A list of the sdk versions to build. Usually just current but can include a
 # numeric version too.
 SDK_VERSIONS = [
@@ -290,20 +368,11 @@
     directory.
     """
 
-    # Destination for stdout from subprocesses.
-    #
-    # This (and the following stderr) are needed to allow the tests to be run
-    # in Intellij. This ensures that the tests are run with stdout/stderr
-    # objects that work when passed to subprocess.run(stdout/stderr). Without it
-    # the tests are run with a FlushingStringIO object that has no fileno
-    # attribute - https://youtrack.jetbrains.com/issue/PY-27883.
-    stdout: io.TextIOBase = sys.stdout
+    # Used to run subprocesses for this.
+    subprocess_runner: SubprocessRunner
 
-    # Destination for stderr from subprocesses.
-    stderr: io.TextIOBase = sys.stderr
-
-    # The OUT_DIR environment variable.
-    out_dir: str = "uninitialized-out"
+    # Builds sdk snapshots
+    snapshot_builder: SnapshotBuilder
 
     # The DIST_DIR environment variable.
     dist_dir: str = "uninitialized-dist"
@@ -312,65 +381,18 @@
     # transformed to document where the changes came from.
     script: str = sys.argv[0]
 
-    def get_sdk_path(self, sdk_name, sdk_version):
-        """Get the path to the sdk snapshot zip file produced by soong"""
-        return os.path.join(self.out_dir, "soong/mainline-sdks",
-                            f"{sdk_name}-{sdk_version}.zip")
-
     def produce_dist(self, modules):
         sdk_versions = SDK_VERSIONS
         self.build_sdks(sdk_versions, modules)
         self.populate_dist(sdk_versions, modules)
+        self.populate_stubs(modules)
 
     def build_sdks(self, sdk_versions, modules):
-        # Build the SDKs once for each version.
-        for sdk_version in sdk_versions:
-            # Compute the paths to all the Soong generated sdk snapshot files
-            # required by this script.
-            paths = [
-                self.get_sdk_path(sdk, sdk_version)
-                for module in modules
-                for sdk in module.sdks
-            ]
-
-            # TODO(ngeoffray): remove SOONG_ALLOW_MISSING_DEPENDENCIES, but we
-            # currently break without it.
-            #
-            # Set SOONG_SDK_SNAPSHOT_USE_SRCJAR to generate .srcjars inside sdk
-            # zip files as expected by prebuilt drop.
-            extraEnv = {
-                "SOONG_ALLOW_MISSING_DEPENDENCIES": "true",
-                "SOONG_SDK_SNAPSHOT_USE_SRCJAR": "true",
-                "SOONG_SDK_SNAPSHOT_VERSION": sdk_version,
-            }
-            # Unless explicitly specified in the calling environment set
-            # TARGET_BUILD_VARIANT=user.
-            # This MUST be identical to the TARGET_BUILD_VARIANT used to build
-            # the corresponding APEXes otherwise it could result in different
-            # hidden API flags, see http://b/202398851#comment29 for more info.
-            targetBuildVariant = os.environ.get("TARGET_BUILD_VARIANT", "user")
-            cmd = [
-                "build/soong/soong_ui.bash",
-                "--make-mode",
-                "--soong-only",
-                f"TARGET_BUILD_VARIANT={targetBuildVariant}",
-                "TARGET_PRODUCT=mainline_sdk",
-                "MODULE_BUILD_FROM_SOURCE=true",
-                "out/soong/apex/depsinfo/new-allowed-deps.txt.check",
-            ] + paths
-            print_command(extraEnv, cmd)
-            env = os.environ.copy()
-            env.update(extraEnv)
-            subprocess.run(
-                cmd,
-                env=env,
-                check=True,
-                stdout=self.stdout,
-                stderr=self.stderr)
+        self.snapshot_builder.build_snapshots(sdk_versions, modules)
 
     def unzip_current_stubs(self, sdk_name, apex_name):
         """Unzips stubs for "current" into {producer.dist_dir}/stubs/{apex}."""
-        sdk_path = self.get_sdk_path(sdk_name, "current")
+        sdk_path = self.snapshot_builder.get_sdk_path(sdk_name, "current")
         dest_dir = os.path.join(self.dist_dir, "stubs", apex_name)
         print(
             f"Extracting java_sdk_library files from {sdk_path} to {dest_dir}")
@@ -378,7 +400,7 @@
         extract_matching_files_from_zip(
             sdk_path, dest_dir, r"sdk_library/[^/]+/[^/]+\.(txt|jar|srcjar)")
 
-    def populate_dist(self, sdk_versions, modules):
+    def populate_stubs(self, modules):
         # TODO(b/199759953): Remove stubs once it is no longer used by gantry.
         # Clear and populate the stubs directory.
         stubs_dir = os.path.join(self.dist_dir, "stubs")
@@ -392,6 +414,7 @@
                 if sdk.endswith("-sdk"):
                     self.unzip_current_stubs(sdk, apex)
 
+    def populate_dist(self, sdk_versions, modules):
         # Clear and populate the mainline-sdks dist directory.
         sdks_dist_dir = os.path.join(self.dist_dir, "mainline-sdks")
         shutil.rmtree(sdks_dist_dir, ignore_errors=True)
@@ -410,7 +433,8 @@
 
                     sdk_dist_dir = os.path.join(sdks_dist_dir, sdk_version,
                                                 apex, subdir)
-                    sdk_path = self.get_sdk_path(sdk, sdk_version)
+                    sdk_path = self.snapshot_builder.get_sdk_path(
+                        sdk, sdk_version)
                     self.dist_sdk_snapshot_zip(sdk_path, sdk_dist_dir,
                                                module.transformations())
 
@@ -480,13 +504,10 @@
     # not affected by a change of directory.
     abs_src_zip_path = os.path.abspath(src_zip_path)
     abs_dest_zip_path = os.path.abspath(dest_zip_path)
-    subprocess.run(
+    producer.subprocess_runner.run(
         ["zip", "-q", abs_src_zip_path, "--out", abs_dest_zip_path] + paths,
         # Change into the source directory before running zip.
-        cwd=src_dir,
-        stdout=producer.stdout,
-        stderr=producer.stderr,
-        check=True)
+        cwd=src_dir)
 
 
 def apply_transformations(producer, tmp_dir, transformations):
@@ -505,12 +526,22 @@
 
 
 def create_producer():
-    return SdkDistProducer(
-        # Variables initialized from environment variables that are set by the
-        # calling mainline_modules_sdks.sh.
-        out_dir=os.environ["OUT_DIR"],
-        dist_dir=os.environ["DIST_DIR"],
+    # Variables initialized from environment variables that are set by the
+    # calling mainline_modules_sdks.sh.
+    out_dir = os.environ["OUT_DIR"]
+    dist_dir = os.environ["DIST_DIR"]
+
+    subprocess_runner = SubprocessRunner()
+    snapshot_builder = SnapshotBuilder(
+        subprocess_runner=subprocess_runner,
+        out_dir=out_dir,
     )
+    return SdkDistProducer(
+        subprocess_runner=subprocess_runner,
+        snapshot_builder=snapshot_builder,
+        dist_dir=dist_dir,
+    )
+
 
 def filter_modules(modules):
     target_build_apps = os.environ.get("TARGET_BUILD_APPS")
@@ -520,6 +551,7 @@
     else:
         return modules
 
+
 def main():
     """Program entry point."""
     if not os.path.exists("build/make/core/Makefile"):
diff --git a/build/mainline_modules_sdks.sh b/build/mainline_modules_sdks.sh
index 4e25161..fb5cfd3 100755
--- a/build/mainline_modules_sdks.sh
+++ b/build/mainline_modules_sdks.sh
@@ -56,7 +56,7 @@
   # provided by the build to ensure consistency across build environments.
   export DIST_DIR OUT_DIR
 
-  prebuilts/build-tools/linux-x86/bin/py3-cmd "${py3script}"
+  prebuilts/build-tools/linux-x86/bin/py3-cmd -u "${py3script}"
 }
 
 init "$@"
diff --git a/build/mainline_modules_sdks_test.py b/build/mainline_modules_sdks_test.py
index 5760136..cf6b28c 100644
--- a/build/mainline_modules_sdks_test.py
+++ b/build/mainline_modules_sdks_test.py
@@ -14,7 +14,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 """Unit tests for mainline_modules_sdks.py."""
-
 from pathlib import Path
 import os
 import tempfile
@@ -23,13 +22,18 @@
 
 import mainline_modules_sdks as mm
 
+MAINLINE_MODULES_BY_APEX = dict((m.apex, m) for m in mm.MAINLINE_MODULES)
 
-class TestPopulateDist(unittest.TestCase):
 
-    def create_snapshot_file(self, out_dir, name, version):
-        sdks_out_dir = Path(out_dir, "soong/mainline-sdks")
-        sdks_out_dir.mkdir(parents=True, exist_ok=True)
-        zip_file = Path(sdks_out_dir, f"{name}-{version}.zip")
+class FakeSnapshotBuilder(mm.SnapshotBuilder):
+    """A fake snapshot builder that does not run the build.
+
+    This skips the whole build process and just creates some fake sdk
+    modules.
+    """
+
+    def create_snapshot_file(self, name, version):
+        zip_file = Path(self.get_sdk_path(name, version))
         with zipfile.ZipFile(zip_file, "w") as z:
             z.writestr("Android.bp", "")
             if name.endswith("-sdk"):
@@ -38,6 +42,19 @@
                 z.writestr("sdk_library/public/lib.jar", "")
                 z.writestr("sdk_library/public/api.txt", "")
 
+    def build_snapshots(self, sdk_versions, modules):
+        # Create input file structure.
+        sdks_out_dir = Path(self.get_mainline_sdks_path())
+        sdks_out_dir.mkdir(parents=True, exist_ok=True)
+        # Create a fake sdk zip file for each module.
+        for module in modules:
+            for sdk in module.sdks:
+                for sdk_version in sdk_versions:
+                    self.create_snapshot_file(sdk, sdk_version)
+
+
+class TestProduceDist(unittest.TestCase):
+
     def test(self):
         """Verify the dist/mainline-sdks directory is populated correctly"""
         with tempfile.TemporaryDirectory() as tmp_dir:
@@ -47,22 +64,24 @@
             os.mkdir(tmp_dist_dir)
 
             modules = [
-                mm.MAINLINE_MODULES_BY_APEX["com.android.art"],
-                mm.MAINLINE_MODULES_BY_APEX["com.android.ipsec"],
+                MAINLINE_MODULES_BY_APEX["com.android.art"],
+                MAINLINE_MODULES_BY_APEX["com.android.ipsec"],
             ]
 
-            # Create input file structure.
-            for module in modules:
-                for sdk in module.sdks:
-                    self.create_snapshot_file(tmp_out_dir, sdk, "current")
+            subprocess_runner = mm.SubprocessRunner()
+
+            snapshot_builder = FakeSnapshotBuilder(
+                subprocess_runner=subprocess_runner,
+                out_dir=tmp_out_dir,
+            )
 
             producer = mm.SdkDistProducer(
-                out_dir=tmp_out_dir,
+                subprocess_runner=subprocess_runner,
+                snapshot_builder=snapshot_builder,
                 dist_dir=tmp_dist_dir,
             )
 
-            sdk_versions = ["current"]
-            producer.populate_dist(sdk_versions, modules)
+            producer.produce_dist(modules)
 
             files = []
             for abs_dir, _, filenames in os.walk(tmp_dist_dir):
@@ -114,7 +133,11 @@
 class TestSoongConfigBoilerplateInserter(unittest.TestCase):
 
     def apply_transformations(self, src, transformations, expected):
-        producer = mm.SdkDistProducer(script=self._testMethodName)
+        producer = mm.SdkDistProducer(
+            subprocess_runner=None,
+            snapshot_builder=None,
+            script=self._testMethodName,
+        )
 
         with tempfile.TemporaryDirectory() as tmp_dir:
             path = os.path.join(tmp_dir, "Android.bp")
@@ -140,7 +163,7 @@
 
         expected = readTestData("ipsec_Android.bp.expected")
 
-        module = mm.MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
+        module = MAINLINE_MODULES_BY_APEX["com.android.ipsec"]
         transformations = module.transformations()
 
         self.apply_transformations(src, transformations, expected)
@@ -156,7 +179,7 @@
 
         expected = readTestData("art_Android.bp.expected")
 
-        module = mm.MAINLINE_MODULES_BY_APEX["com.android.art"]
+        module = MAINLINE_MODULES_BY_APEX["com.android.art"]
         transformations = module.transformations()
 
         self.apply_transformations(src, transformations, expected)
@@ -171,7 +194,7 @@
     def test_with_filter(self):
         os.environ["TARGET_BUILD_APPS"] = "com.android.art"
         modules = mm.filter_modules(mm.MAINLINE_MODULES)
-        expected = mm.MAINLINE_MODULES_BY_APEX["com.android.art"]
+        expected = MAINLINE_MODULES_BY_APEX["com.android.art"]
         self.assertEqual(modules, [expected])