Run test build: Indent code

Test: Generated artifacts are identical
Change-Id: I8590507130b338d7be14556df4238c81d0945cb6
diff --git a/test/run_test_build.py b/test/run_test_build.py
index 05184ed..34d569d 100755
--- a/test/run_test_build.py
+++ b/test/run_test_build.py
@@ -97,409 +97,398 @@
                           env=self.bash_env,
                           check=True)
 
-  def default_build(self, **kwargs):
-    globals()['default_build'](self, **kwargs)
+  def default_build(
+      ctx,
+      use_desugar=True,
+      use_hiddenapi=True,
+      need_dex=None,
+      experimental="no-experiment",
+      zip_compression_method="deflate",
+      zip_align_bytes=None,
+      api_level=None,
+      javac_args=[],
+      d8_flags=[],
+      smali_args=[],
+      use_smali=True,
+      use_jasmin=True,
+    ):
 
-def rm(*patterns):
-  for pattern in patterns:
-    for path in glob.glob(pattern):
-      if os.path.isdir(path):
-        shutil.rmtree(path)
-      else:
-        os.remove(path)
+    # Wrap "pathlib.Path" with our own version that ensures all paths are absolute.
+    # Plain filenames are assumed to be relative to ctx.test_dir and made absolute.
+    class Path(pathlib.Path):
+      def __new__(cls, filename: str):
+        path = pathlib.Path(filename)
+        return path if path.is_absolute() else (ctx.test_dir / path)
 
-def default_build(
-    ctx,
-    use_desugar=True,
-    use_hiddenapi=True,
-    need_dex=None,
-    experimental="no-experiment",
-    zip_compression_method="deflate",
-    zip_align_bytes=None,
-    api_level=None,
-    javac_args=[],
-    d8_flags=[],
-    smali_args=[],
-    use_smali=True,
-    use_jasmin=True,
-  ):
+    ANDROID_BUILD_TOP = ctx.android_build_top
+    TEST_NAME = ctx.test_name
+    need_dex = (ctx.host or ctx.target) if need_dex is None else need_dex
 
-  # Wrap "pathlib.Path" with our own version that ensures all paths are absolute.
-  # Plain filenames are assumed to be relative to ctx.test_dir and made absolute.
-  class Path(pathlib.Path):
-    def __new__(cls, filename: str):
-      path = pathlib.Path(filename)
-      return path if path.is_absolute() else (ctx.test_dir / path)
+    RBE_exec_root = os.environ.get("RBE_exec_root")
+    RBE_rewrapper = ctx.android_build_top / "prebuilts/remoteexecution-client/live/rewrapper"
 
-  ANDROID_BUILD_TOP = ctx.android_build_top
-  TEST_NAME = ctx.test_name
-  need_dex = (ctx.host or ctx.target) if need_dex is None else need_dex
+    # Set default values for directories.
+    HAS_SRC = Path("src").exists()
+    HAS_SRC_ART = Path("src-art").exists()
+    HAS_SRC2 = Path("src2").exists()
+    HAS_SRC_MULTIDEX = Path("src-multidex").exists()
+    HAS_SMALI_MULTIDEX = Path("smali-multidex").exists()
+    HAS_JASMIN_MULTIDEX = Path("jasmin-multidex").exists()
+    HAS_SMALI_EX = Path("smali-ex").exists()
+    HAS_SRC_EX = Path("src-ex").exists()
+    HAS_SRC_EX2 = Path("src-ex2").exists()
+    HAS_SRC_AOTEX = Path("src-aotex").exists()
+    HAS_SRC_BCPEX = Path("src-bcpex").exists()
+    HAS_HIDDENAPI_SPEC = Path("hiddenapi-flags.csv").exists()
 
-  RBE_exec_root = os.environ.get("RBE_exec_root")
-  RBE_rewrapper = ctx.android_build_top / "prebuilts/remoteexecution-client/live/rewrapper"
+    JAVAC_ARGS = shlex.split(ctx.javac_args) + javac_args
+    SMALI_ARGS = smali_args.copy()
+    D8_FLAGS = d8_flags.copy()
 
-  # Set default values for directories.
-  HAS_SRC = Path("src").exists()
-  HAS_SRC_ART = Path("src-art").exists()
-  HAS_SRC2 = Path("src2").exists()
-  HAS_SRC_MULTIDEX = Path("src-multidex").exists()
-  HAS_SMALI_MULTIDEX = Path("smali-multidex").exists()
-  HAS_JASMIN_MULTIDEX = Path("jasmin-multidex").exists()
-  HAS_SMALI_EX = Path("smali-ex").exists()
-  HAS_SRC_EX = Path("src-ex").exists()
-  HAS_SRC_EX2 = Path("src-ex2").exists()
-  HAS_SRC_AOTEX = Path("src-aotex").exists()
-  HAS_SRC_BCPEX = Path("src-bcpex").exists()
-  HAS_HIDDENAPI_SPEC = Path("hiddenapi-flags.csv").exists()
+    BUILD_MODE = ctx.mode
 
-  JAVAC_ARGS = shlex.split(ctx.javac_args) + javac_args
-  SMALI_ARGS = smali_args.copy()
-  D8_FLAGS = d8_flags.copy()
+    # Setup experimental API level mappings in a bash associative array.
+    EXPERIMENTAL_API_LEVEL = {}
+    EXPERIMENTAL_API_LEVEL["no-experiment"] = "26"
+    EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
+    EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
+    EXPERIMENTAL_API_LEVEL["agents"] = "26"
+    EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
+    EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
 
-  BUILD_MODE = ctx.mode
+    if BUILD_MODE == "jvm":
+      # No desugaring on jvm because it supports the latest functionality.
+      use_desugar = False
+      # Do not attempt to build src-art directories on jvm,
+      # since it would fail without libcore.
+      HAS_SRC_ART = False
 
-  # Setup experimental API level mappings in a bash associative array.
-  EXPERIMENTAL_API_LEVEL = {}
-  EXPERIMENTAL_API_LEVEL["no-experiment"] = "26"
-  EXPERIMENTAL_API_LEVEL["default-methods"] = "24"
-  EXPERIMENTAL_API_LEVEL["parameter-annotations"] = "25"
-  EXPERIMENTAL_API_LEVEL["agents"] = "26"
-  EXPERIMENTAL_API_LEVEL["method-handles"] = "26"
-  EXPERIMENTAL_API_LEVEL["var-handles"] = "28"
+    # Set API level for smali and d8.
+    if not api_level:
+      api_level = EXPERIMENTAL_API_LEVEL[experimental]
 
-  if BUILD_MODE == "jvm":
-    # No desugaring on jvm because it supports the latest functionality.
-    use_desugar = False
-    # Do not attempt to build src-art directories on jvm,
-    # since it would fail without libcore.
-    HAS_SRC_ART = False
+    # Add API level arguments to smali and dx
+    SMALI_ARGS.extend(["--api", str(api_level)])
+    D8_FLAGS.extend(["--min-api", str(api_level)])
 
-  # Set API level for smali and d8.
-  if not api_level:
-    api_level = EXPERIMENTAL_API_LEVEL[experimental]
-
-  # Add API level arguments to smali and dx
-  SMALI_ARGS.extend(["--api", str(api_level)])
-  D8_FLAGS.extend(["--min-api", str(api_level)])
-
-  def run(executable: pathlib.Path, args: List[str]):
-    assert isinstance(executable, pathlib.Path), executable
-    cmd: List[Union[pathlib.Path, str]] = []
-    if executable.suffix == ".sh":
-      cmd += ["/bin/bash"]
-    cmd += [executable]
-    cmd += args
-    env = ctx.bash_env
-    env.update({k: v for k, v in os.environ.items() if k.startswith("RBE_")})
-    # Make paths relative as otherwise we could create too long command line.
-    for i, arg in enumerate(cmd):
-      if isinstance(arg, pathlib.Path):
-        assert arg.absolute(), arg
-        cmd[i] = relpath(arg, ctx.test_dir)
-      elif isinstance(arg, list):
-        assert all(p.absolute() for p in arg), arg
-        cmd[i] = ":".join(relpath(p, ctx.test_dir) for p in arg)
-      else:
-        assert isinstance(arg, str), arg
-    p = subprocess.run(cmd,
-                       encoding=sys.stdout.encoding,
-                       cwd=ctx.test_dir,
-                       env=ctx.bash_env,
-                       stderr=subprocess.STDOUT,
-                       stdout=subprocess.PIPE)
-    if p.returncode != 0:
-      raise Exception("Command failed with exit code {}\n$ {}\n{}".format(
-                      p.returncode, " ".join(map(str, cmd)), p.stdout))
-    return p
+    def run(executable: pathlib.Path, args: List[str]):
+      assert isinstance(executable, pathlib.Path), executable
+      cmd: List[Union[pathlib.Path, str]] = []
+      if executable.suffix == ".sh":
+        cmd += ["/bin/bash"]
+      cmd += [executable]
+      cmd += args
+      env = ctx.bash_env
+      env.update({k: v for k, v in os.environ.items() if k.startswith("RBE_")})
+      # Make paths relative as otherwise we could create too long command line.
+      for i, arg in enumerate(cmd):
+        if isinstance(arg, pathlib.Path):
+          assert arg.absolute(), arg
+          cmd[i] = relpath(arg, ctx.test_dir)
+        elif isinstance(arg, list):
+          assert all(p.absolute() for p in arg), arg
+          cmd[i] = ":".join(relpath(p, ctx.test_dir) for p in arg)
+        else:
+          assert isinstance(arg, str), arg
+      p = subprocess.run(cmd,
+                         encoding=sys.stdout.encoding,
+                         cwd=ctx.test_dir,
+                         env=ctx.bash_env,
+                         stderr=subprocess.STDOUT,
+                         stdout=subprocess.PIPE)
+      if p.returncode != 0:
+        raise Exception("Command failed with exit code {}\n$ {}\n{}".format(
+                        p.returncode, " ".join(map(str, cmd)), p.stdout))
+      return p
 
 
-  # Helper functions to execute tools.
-  soong_zip = functools.partial(run, ctx.soong_zip)
-  zipalign = functools.partial(run, ctx.zipalign)
-  javac = functools.partial(run, ctx.javac)
-  jasmin = functools.partial(run, ctx.jasmin)
-  smali = functools.partial(run, ctx.smali)
-  d8 = functools.partial(run, ctx.d8)
-  hiddenapi = functools.partial(run, ctx.hiddenapi)
+    # Helper functions to execute tools.
+    soong_zip = functools.partial(run, ctx.soong_zip)
+    zipalign = functools.partial(run, ctx.zipalign)
+    javac = functools.partial(run, ctx.javac)
+    jasmin = functools.partial(run, ctx.jasmin)
+    smali = functools.partial(run, ctx.smali)
+    d8 = functools.partial(run, ctx.d8)
+    hiddenapi = functools.partial(run, ctx.hiddenapi)
 
-  if "RBE_server_address" in os.environ:
-    version = match(r"Version: (\d*)\.(\d*)\.(\d*)", run(RBE_rewrapper, ["--version"]).stdout)
-    assert version, "Could not parse RBE version"
-    assert tuple(map(int, version.groups())) >= (0, 76, 0), "Please update " + RBE_rewrapper
+    if "RBE_server_address" in os.environ:
+      version = match(r"Version: (\d*)\.(\d*)\.(\d*)", run(RBE_rewrapper, ["--version"]).stdout)
+      assert version, "Could not parse RBE version"
+      assert tuple(map(int, version.groups())) >= (0, 76, 0), "Please update " + RBE_rewrapper
 
-    def rbe_wrap(args, inputs: Set[pathlib.Path]=None):
-      with NamedTemporaryFile(mode="w+t") as input_list:
-        inputs = inputs or set()
-        for i, arg in enumerate(args):
-          if isinstance(arg, pathlib.Path):
-            assert arg.absolute(), arg
-            inputs.add(arg)
-          elif isinstance(arg, list):
-            assert all(p.absolute() for p in arg), arg
-            inputs.update(arg)
-        input_list.writelines([relpath(i, RBE_exec_root)+"\n" for i in inputs])
-        input_list.flush()
-        return run(RBE_rewrapper, [
-          "--platform=" + os.environ["RBE_platform"],
-          "--input_list_paths=" + input_list.name,
-        ] + args)
+      def rbe_wrap(args, inputs: Set[pathlib.Path]=None):
+        with NamedTemporaryFile(mode="w+t") as input_list:
+          inputs = inputs or set()
+          for i, arg in enumerate(args):
+            if isinstance(arg, pathlib.Path):
+              assert arg.absolute(), arg
+              inputs.add(arg)
+            elif isinstance(arg, list):
+              assert all(p.absolute() for p in arg), arg
+              inputs.update(arg)
+          input_list.writelines([relpath(i, RBE_exec_root)+"\n" for i in inputs])
+          input_list.flush()
+          return run(RBE_rewrapper, [
+            "--platform=" + os.environ["RBE_platform"],
+            "--input_list_paths=" + input_list.name,
+          ] + args)
 
-    if USE_RBE_FOR_JAVAC > (hash(TEST_NAME) % 100):  # Use for given percentage of tests.
-      def javac(args):
-        output = relpath(Path(args[args.index("-d") + 1]), RBE_exec_root)
-        return rbe_wrap(["--output_directories", output, ctx.javac] + args)
+      if USE_RBE_FOR_JAVAC > (hash(TEST_NAME) % 100):  # Use for given percentage of tests.
+        def javac(args):
+          output = relpath(Path(args[args.index("-d") + 1]), RBE_exec_root)
+          return rbe_wrap(["--output_directories", output, ctx.javac] + args)
 
-    if USE_RBE_FOR_D8 > (hash(TEST_NAME) % 100):  # Use for given percentage of tests.
-      def d8(args):
-        inputs = set([ctx.d8.parent.parent / "framework/d8.jar"])
-        output = relpath(Path(args[args.index("--output") + 1]), RBE_exec_root)
-        return rbe_wrap([
-          "--output_files" if output.endswith(".jar") else "--output_directories", output,
-          "--toolchain_inputs=prebuilts/jdk/jdk11/linux-x86/bin/java",
-          ctx.d8] + args, inputs)
+      if USE_RBE_FOR_D8 > (hash(TEST_NAME) % 100):  # Use for given percentage of tests.
+        def d8(args):
+          inputs = set([ctx.d8.parent.parent / "framework/d8.jar"])
+          output = relpath(Path(args[args.index("--output") + 1]), RBE_exec_root)
+          return rbe_wrap([
+            "--output_files" if output.endswith(".jar") else "--output_directories", output,
+            "--toolchain_inputs=prebuilts/jdk/jdk11/linux-x86/bin/java",
+            ctx.d8] + args, inputs)
 
-  # If wrapper script exists, use it instead of the default javac.
-  javac_wrapper = Path("javac_wrapper.sh")
-  if javac_wrapper.exists():
-    javac = functools.partial(run, javac_wrapper)
+    # If wrapper script exists, use it instead of the default javac.
+    javac_wrapper = Path("javac_wrapper.sh")
+    if javac_wrapper.exists():
+      javac = functools.partial(run, javac_wrapper)
 
-  def zip(zip_target: Path, *files: Path):
-    zip_args = ["-o", zip_target, "-C", zip_target.parent]
-    if zip_compression_method == "store":
-      zip_args.extend(["-L", "0"])
-    for f in files:
-      zip_args.extend(["-f", f])
-    soong_zip(zip_args)
+    def zip(zip_target: Path, *files: Path):
+      zip_args = ["-o", zip_target, "-C", zip_target.parent]
+      if zip_compression_method == "store":
+        zip_args.extend(["-L", "0"])
+      for f in files:
+        zip_args.extend(["-f", f])
+      soong_zip(zip_args)
 
-    if zip_align_bytes:
-      # zipalign does not operate in-place, so write results to a temp file.
+      if zip_align_bytes:
+        # zipalign does not operate in-place, so write results to a temp file.
+        with TemporaryDirectory() as tmp_dir:
+          tmp_file = Path(tmp_dir) / "aligned.zip"
+          zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file])
+          # replace original zip target with our temp file.
+          tmp_file.rename(zip_target)
+
+
+    def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]:
+      if not use_jasmin or not src_dir.exists():
+        return None  # No sources to compile.
+      dst_dir.mkdir()
+      jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j")))
+      return dst_dir
+
+    def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]:
+      if not use_smali or not src_dir.exists():
+        return None  # No sources to compile.
+      smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
+            ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali")))
+      return dst_dex
+
+
+    java_classpath: List[Path] = []
+
+    def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]:
+      if not any(src_dir.exists() for src_dir in src_dirs):
+        return None  # No sources to compile.
+      dst_dir.mkdir(exist_ok=True)
+      args = JAVAC_ARGS + ["-implicit:none", "-encoding", "utf8", "-d", dst_dir]
+      if not ctx.jvm:
+        args += ["-bootclasspath", ctx.bootclasspath]
+      if java_classpath:
+        args += ["-classpath", java_classpath]
+      for src_dir in src_dirs:
+        args += sorted(src_dir.glob("**/*.java"))
+      javac(args)
+      return dst_dir
+
+
+    # Make a "dex" file given a directory of classes. This will be
+    # packaged in a jar file.
+    def make_dex(src_dir: Path):
+      dst_jar = Path(src_dir.name + ".jar")
+      args = D8_FLAGS + ["--output", dst_jar]
+      args += ["--lib", ctx.bootclasspath] if use_desugar else ["--no-desugaring"]
+      args += sorted(src_dir.glob("**/*.class"))
+      d8(args)
+
+      # D8 outputs to JAR files today rather than DEX files as DX used
+      # to. To compensate, we extract the DEX from d8's output to meet the
+      # expectations of make_dex callers.
+      dst_dex = Path(src_dir.name + ".dex")
       with TemporaryDirectory() as tmp_dir:
-        tmp_file = Path(tmp_dir) / "aligned.zip"
-        zipalign(["-f", str(zip_align_bytes), zip_target, tmp_file])
-        # replace original zip target with our temp file.
-        tmp_file.rename(zip_target)
+        zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir)
+        (Path(tmp_dir) / "classes.dex").rename(dst_dex)
+
+    # Merge all the dex files.
+    # Skip non-existing files, but at least 1 file must exist.
+    def make_dexmerge(dst_dex: Path, *src_dexs: Path):
+      # Include destination. Skip any non-existing files.
+      srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()]
+
+      # NB: We merge even if there is just single input.
+      # It is useful to normalize non-deterministic smali output.
+      tmp_dir = ctx.test_dir / "dexmerge"
+      tmp_dir.mkdir()
+      d8(["--min-api", api_level, "--output", tmp_dir] + srcs)
+      assert not (tmp_dir / "classes2.dex").exists()
+      for src_file in srcs:
+        src_file.unlink()
+      (tmp_dir / "classes.dex").rename(dst_dex)
+      tmp_dir.rmdir()
 
 
-  def make_jasmin(dst_dir: Path, src_dir: Path) -> Optional[Path]:
-    if not use_jasmin or not src_dir.exists():
-      return None  # No sources to compile.
-    dst_dir.mkdir()
-    jasmin(["-d", dst_dir] + sorted(src_dir.glob("**/*.j")))
-    return dst_dir
-
-  def make_smali(dst_dex: Path, src_dir: Path) -> Optional[Path]:
-    if not use_smali or not src_dir.exists():
-      return None  # No sources to compile.
-    smali(["-JXmx512m", "assemble"] + SMALI_ARGS +
-          ["--output", dst_dex] + sorted(src_dir.glob("**/*.smali")))
-    return dst_dex
+    def make_hiddenapi(*dex_files: Path):
+      args: List[Union[str, Path]] = ["encode"]
+      for dex_file in dex_files:
+        args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)])
+      args.append("--api-flags=hiddenapi-flags.csv")
+      args.append("--no-force-assign-all")
+      hiddenapi(args)
 
 
-  java_classpath: List[Path] = []
-
-  def make_java(dst_dir: Path, *src_dirs: Path) -> Optional[Path]:
-    if not any(src_dir.exists() for src_dir in src_dirs):
-      return None  # No sources to compile.
-    dst_dir.mkdir(exist_ok=True)
-    args = JAVAC_ARGS + ["-implicit:none", "-encoding", "utf8", "-d", dst_dir]
-    if not ctx.jvm:
-      args += ["-bootclasspath", ctx.bootclasspath]
-    if java_classpath:
-      args += ["-classpath", java_classpath]
-    for src_dir in src_dirs:
-      args += sorted(src_dir.glob("**/*.java"))
-    javac(args)
-    return dst_dir
-
-
-  # Make a "dex" file given a directory of classes. This will be
-  # packaged in a jar file.
-  def make_dex(src_dir: Path):
-    dst_jar = Path(src_dir.name + ".jar")
-    args = D8_FLAGS + ["--output", dst_jar]
-    args += ["--lib", ctx.bootclasspath] if use_desugar else ["--no-desugaring"]
-    args += sorted(src_dir.glob("**/*.class"))
-    d8(args)
-
-    # D8 outputs to JAR files today rather than DEX files as DX used
-    # to. To compensate, we extract the DEX from d8's output to meet the
-    # expectations of make_dex callers.
-    dst_dex = Path(src_dir.name + ".dex")
-    with TemporaryDirectory() as tmp_dir:
-      zipfile.ZipFile(dst_jar, "r").extractall(tmp_dir)
-      (Path(tmp_dir) / "classes.dex").rename(dst_dex)
-
-  # Merge all the dex files.
-  # Skip non-existing files, but at least 1 file must exist.
-  def make_dexmerge(dst_dex: Path, *src_dexs: Path):
-    # Include destination. Skip any non-existing files.
-    srcs = [f for f in [dst_dex] + list(src_dexs) if f.exists()]
-
-    # NB: We merge even if there is just single input.
-    # It is useful to normalize non-deterministic smali output.
-    tmp_dir = ctx.test_dir / "dexmerge"
-    tmp_dir.mkdir()
-    d8(["--min-api", api_level, "--output", tmp_dir] + srcs)
-    assert not (tmp_dir / "classes2.dex").exists()
-    for src_file in srcs:
-      src_file.unlink()
-    (tmp_dir / "classes.dex").rename(dst_dex)
-    tmp_dir.rmdir()
-
-
-  def make_hiddenapi(*dex_files: Path):
-    args: List[Union[str, Path]] = ["encode"]
-    for dex_file in dex_files:
-      args.extend(["--input-dex=" + str(dex_file), "--output-dex=" + str(dex_file)])
-    args.append("--api-flags=hiddenapi-flags.csv")
-    args.append("--no-force-assign-all")
-    hiddenapi(args)
-
-
-  if Path("classes.dex").exists():
-    zip(Path(TEST_NAME + ".jar"), Path("classes.dex"))
-    return
-
-  if Path("classes.dm").exists():
-    zip(Path(TEST_NAME + ".jar"), Path("classes.dm"))
-    return
-
-
-  def has_multidex():
-    return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
-
-
-  if make_jasmin(Path("jasmin_classes"), Path("jasmin")):
-    java_classpath.append(Path("jasmin_classes"))
-
-  if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")):
-    java_classpath.append(Path("jasmin_classes2"))
-
-  if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
-                  HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
-    # To allow circular references, compile src/, src-multidex/, src-aotex/,
-    # src-bcpex/, src-ex/ together and pass the output as class path argument.
-    # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
-    # used by the other src-* sources we compile here but everything needed to
-    # compile the other src-* sources should be present in src/ (and jasmin*/).
-    make_java(Path("classes-tmp-all"),
-              Path("src"),
-              Path("src-multidex"),
-              Path("src-aotex"),
-              Path("src-bcpex"),
-              Path("src-ex"))
-    java_classpath.append(Path("classes-tmp-all"))
-
-  if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex:
-    make_dex(Path("classes-aotex"))
-    # rename it so it shows up as "classes.dex" in the zip file.
-    Path("classes-aotex.dex").rename(Path("classes.dex"))
-    zip(Path(TEST_NAME + "-aotex.jar"), Path("classes.dex"))
-
-  if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex:
-    make_dex(Path("classes-bcpex"))
-    # rename it so it shows up as "classes.dex" in the zip file.
-    Path("classes-bcpex.dex").rename(Path("classes.dex"))
-    zip(Path(TEST_NAME + "-bcpex.jar"), Path("classes.dex"))
-
-  make_java(Path("classes"), Path("src"))
-
-  if not ctx.jvm:
-    # Do not attempt to build src-art directories on jvm,
-    # since it would fail without libcore.
-    make_java(Path("classes"), Path("src-art"))
-
-  if make_java(Path("classes2"), Path("src-multidex")) and need_dex:
-    make_dex(Path("classes2"))
-
-  make_java(Path("classes"), Path("src2"))
-
-  # If the classes directory is not-empty, package classes in a DEX file.
-  # NB: some tests provide classes rather than java files.
-  if any(Path("classes").glob("*")) and need_dex:
-    make_dex(Path("classes"))
-
-  if Path("jasmin_classes").exists():
-    # Compile Jasmin classes as if they were part of the classes.dex file.
-    if need_dex:
-      make_dex(Path("jasmin_classes"))
-      make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex"))
-    else:
-      # Move jasmin classes into classes directory so that they are picked up
-      # with -cp classes.
-      Path("classes").mkdir(exist_ok=True)
-      copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True)
-
-  if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")):
-    # Merge smali files into classes.dex,
-    # this takes priority over any jasmin files.
-    make_dexmerge(Path("classes.dex"), Path("smali_classes.dex"))
-
-  # Compile Jasmin classes in jasmin-multidex as if they were part of
-  # the classes2.jar
-  if HAS_JASMIN_MULTIDEX:
-    if need_dex:
-      make_dex(Path("jasmin_classes2"))
-      make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex"))
-    else:
-      # Move jasmin classes into classes2 directory so that
-      # they are picked up with -cp classes2.
-      Path("classes2").mkdir()
-      copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True)
-      rmtree(Path("jasmin_classes2"))
-
-  if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")):
-    # Merge smali_classes2.dex into classes2.dex
-    make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex"))
-
-  make_java(Path("classes-ex"), Path("src-ex"))
-
-  make_java(Path("classes-ex"), Path("src-ex2"))
-
-  if Path("classes-ex").exists() and need_dex:
-    make_dex(Path("classes-ex"))
-
-  if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")):
-    # Merge smali files into classes-ex.dex.
-    make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex"))
-
-  if Path("classes-ex.dex").exists():
-    # Apply hiddenapi on the dex files if the test has API list file(s).
-    if use_hiddenapi and HAS_HIDDENAPI_SPEC:
-      make_hiddenapi(Path("classes-ex.dex"))
-
-    # quick shuffle so that the stored name is "classes.dex"
-    Path("classes.dex").rename(Path("classes-1.dex"))
-    Path("classes-ex.dex").rename(Path("classes.dex"))
-    zip(Path(TEST_NAME + "-ex.jar"), Path("classes.dex"))
-    Path("classes.dex").rename(Path("classes-ex.dex"))
-    Path("classes-1.dex").rename(Path("classes.dex"))
-
-  # Apply hiddenapi on the dex files if the test has API list file(s).
-  if need_dex and use_hiddenapi and HAS_HIDDENAPI_SPEC:
-    if has_multidex():
-      make_hiddenapi(Path("classes.dex"), Path("classes2.dex"))
-    else:
-      make_hiddenapi(Path("classes.dex"))
-
-  # Create a single dex jar with two dex files for multidex.
-  if need_dex:
-    if Path("classes2.dex").exists():
-      zip(Path(TEST_NAME + ".jar"), Path("classes.dex"), Path("classes2.dex"))
-    else:
+    if Path("classes.dex").exists():
       zip(Path(TEST_NAME + ".jar"), Path("classes.dex"))
+      return
+
+    if Path("classes.dm").exists():
+      zip(Path(TEST_NAME + ".jar"), Path("classes.dm"))
+      return
 
 
-def build_test(ctx: BuildTestContext) -> None:
-  """Run the build script for single run-test"""
+    def has_multidex():
+      return HAS_SRC_MULTIDEX or HAS_JASMIN_MULTIDEX or HAS_SMALI_MULTIDEX
 
-  script = ctx.test_dir / "build.py"
-  if script.exists():
-    module = SourceFileLoader("build_" + ctx.test_name,
-                              str(script)).load_module()
-    module.build(ctx)
-  else:
-    default_build(ctx)
+
+    if make_jasmin(Path("jasmin_classes"), Path("jasmin")):
+      java_classpath.append(Path("jasmin_classes"))
+
+    if make_jasmin(Path("jasmin_classes2"), Path("jasmin-multidex")):
+      java_classpath.append(Path("jasmin_classes2"))
+
+    if HAS_SRC and (HAS_SRC_MULTIDEX or HAS_SRC_AOTEX or HAS_SRC_BCPEX or
+                    HAS_SRC_EX or HAS_SRC_ART or HAS_SRC2 or HAS_SRC_EX2):
+      # To allow circular references, compile src/, src-multidex/, src-aotex/,
+      # src-bcpex/, src-ex/ together and pass the output as class path argument.
+      # Replacement sources in src-art/, src2/ and src-ex2/ can replace symbols
+      # used by the other src-* sources we compile here but everything needed to
+      # compile the other src-* sources should be present in src/ (and jasmin*/).
+      make_java(Path("classes-tmp-all"),
+                Path("src"),
+                Path("src-multidex"),
+                Path("src-aotex"),
+                Path("src-bcpex"),
+                Path("src-ex"))
+      java_classpath.append(Path("classes-tmp-all"))
+
+    if make_java(Path("classes-aotex"), Path("src-aotex")) and need_dex:
+      make_dex(Path("classes-aotex"))
+      # rename it so it shows up as "classes.dex" in the zip file.
+      Path("classes-aotex.dex").rename(Path("classes.dex"))
+      zip(Path(TEST_NAME + "-aotex.jar"), Path("classes.dex"))
+
+    if make_java(Path("classes-bcpex"), Path("src-bcpex")) and need_dex:
+      make_dex(Path("classes-bcpex"))
+      # rename it so it shows up as "classes.dex" in the zip file.
+      Path("classes-bcpex.dex").rename(Path("classes.dex"))
+      zip(Path(TEST_NAME + "-bcpex.jar"), Path("classes.dex"))
+
+    make_java(Path("classes"), Path("src"))
+
+    if not ctx.jvm:
+      # Do not attempt to build src-art directories on jvm,
+      # since it would fail without libcore.
+      make_java(Path("classes"), Path("src-art"))
+
+    if make_java(Path("classes2"), Path("src-multidex")) and need_dex:
+      make_dex(Path("classes2"))
+
+    make_java(Path("classes"), Path("src2"))
+
+    # If the classes directory is not-empty, package classes in a DEX file.
+    # NB: some tests provide classes rather than java files.
+    if any(Path("classes").glob("*")) and need_dex:
+      make_dex(Path("classes"))
+
+    if Path("jasmin_classes").exists():
+      # Compile Jasmin classes as if they were part of the classes.dex file.
+      if need_dex:
+        make_dex(Path("jasmin_classes"))
+        make_dexmerge(Path("classes.dex"), Path("jasmin_classes.dex"))
+      else:
+        # Move jasmin classes into classes directory so that they are picked up
+        # with -cp classes.
+        Path("classes").mkdir(exist_ok=True)
+        copytree(Path("jasmin_classes"), Path("classes"), dirs_exist_ok=True)
+
+    if need_dex and make_smali(Path("smali_classes.dex"), Path("smali")):
+      # Merge smali files into classes.dex,
+      # this takes priority over any jasmin files.
+      make_dexmerge(Path("classes.dex"), Path("smali_classes.dex"))
+
+    # Compile Jasmin classes in jasmin-multidex as if they were part of
+    # the classes2.jar
+    if HAS_JASMIN_MULTIDEX:
+      if need_dex:
+        make_dex(Path("jasmin_classes2"))
+        make_dexmerge(Path("classes2.dex"), Path("jasmin_classes2.dex"))
+      else:
+        # Move jasmin classes into classes2 directory so that
+        # they are picked up with -cp classes2.
+        Path("classes2").mkdir()
+        copytree(Path("jasmin_classes2"), Path("classes2"), dirs_exist_ok=True)
+        rmtree(Path("jasmin_classes2"))
+
+    if need_dex and make_smali(Path("smali_classes2.dex"), Path("smali-multidex")):
+      # Merge smali_classes2.dex into classes2.dex
+      make_dexmerge(Path("classes2.dex"), Path("smali_classes2.dex"))
+
+    make_java(Path("classes-ex"), Path("src-ex"))
+
+    make_java(Path("classes-ex"), Path("src-ex2"))
+
+    if Path("classes-ex").exists() and need_dex:
+      make_dex(Path("classes-ex"))
+
+    if need_dex and make_smali(Path("smali_classes-ex.dex"), Path("smali-ex")):
+      # Merge smali files into classes-ex.dex.
+      make_dexmerge(Path("classes-ex.dex"), Path("smali_classes-ex.dex"))
+
+    if Path("classes-ex.dex").exists():
+      # Apply hiddenapi on the dex files if the test has API list file(s).
+      if use_hiddenapi and HAS_HIDDENAPI_SPEC:
+        make_hiddenapi(Path("classes-ex.dex"))
+
+      # quick shuffle so that the stored name is "classes.dex"
+      Path("classes.dex").rename(Path("classes-1.dex"))
+      Path("classes-ex.dex").rename(Path("classes.dex"))
+      zip(Path(TEST_NAME + "-ex.jar"), Path("classes.dex"))
+      Path("classes.dex").rename(Path("classes-ex.dex"))
+      Path("classes-1.dex").rename(Path("classes.dex"))
+
+    # Apply hiddenapi on the dex files if the test has API list file(s).
+    if need_dex and use_hiddenapi and HAS_HIDDENAPI_SPEC:
+      if has_multidex():
+        make_hiddenapi(Path("classes.dex"), Path("classes2.dex"))
+      else:
+        make_hiddenapi(Path("classes.dex"))
+
+    # Create a single dex jar with two dex files for multidex.
+    if need_dex:
+      if Path("classes2.dex").exists():
+        zip(Path(TEST_NAME + ".jar"), Path("classes.dex"), Path("classes2.dex"))
+      else:
+        zip(Path(TEST_NAME + ".jar"), Path("classes.dex"))
+
+
+  def build_test(ctx) -> None:
+    """Run the build script for single run-test"""
+
+    script = ctx.test_dir / "build.py"
+    if script.exists():
+      module = SourceFileLoader("build_" + ctx.test_name,
+                                str(script)).load_module()
+      module.build(ctx)
+    else:
+      ctx.default_build()
 
 
 # If we build just individual shard, we want to split the work among all the cores,
@@ -551,7 +540,7 @@
     with ThreadPoolExecutor(cpu_count() if use_multiprocessing(args.mode) else 1) as pool:
       jobs = {}
       for ctx in tests:
-        jobs[ctx.test_name] = pool.submit(build_test, ctx)
+        jobs[ctx.test_name] = pool.submit(ctx.build_test)
       for test_name, job in jobs.items():
         try:
           job.result()