blob: 30a966d101f00989945b6da6bda4d9d6eb935f36 [file] [log] [blame]
#!/usr/bin/env python3
#
# 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.
"""
This scripts compiles Java files which are needed to execute run-tests.
It is intended to be used only from soong genrule.
"""
from argparse import ArgumentParser
from art_build_rules import BuildTestContext, default_build
from fcntl import lockf, LOCK_EX, LOCK_NB
from importlib.machinery import SourceFileLoader
from multiprocessing import Pool
from multiprocessing.pool import ApplyResult
from os import environ, getcwd, chdir, cpu_count
from os.path import join, basename
from pathlib import Path
from re import match
from shutil import copytree
from subprocess import run
from typing import Dict
ZIP = "prebuilts/build-tools/linux-x86/bin/soong_zip"
lock_file = None # Keep alive as long as this process is alive.
def copy_sources(args, ziproot: Path, mode: str, srcdir: Path) -> Path:
"""Copy test files from Android tree into the build sandbox and return its path."""
dstdir = ziproot / mode / srcdir.name
copytree(srcdir, dstdir)
return dstdir
def build_test(ctx: BuildTestContext) -> None:
"""Run the build script for single run-test"""
chdir(ctx.test_dir)
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 we build just individual shard, we want to split the work among all the cores,
# but if the build system builds all shards, we don't want to overload the machine.
# We don't know which situation we are in, so as simple work-around, we use a lock
# file to allow only one shard to use multiprocessing at the same time.
def use_multiprocessing(mode: str) -> bool:
global lock_file
lock_path = join(environ["TMPDIR"], "art-test-run-test-build-py-" + mode)
lock_file = open(lock_path, "w")
try:
lockf(lock_file, LOCK_EX | LOCK_NB)
return True # We are the only instance of this script in the build system.
except BlockingIOError:
return False # Some other instance is already running.
def main() -> None:
parser = ArgumentParser(description=__doc__)
parser.add_argument(
"--out", help="Path of the generated ZIP file with the build data")
parser.add_argument("--mode", choices=["host", "jvm", "target"])
parser.add_argument(
"--shard", help="Identifies subset of tests to build (00..99)")
parser.add_argument(
"--bootclasspath", help="JAR files used for javac compilation")
args = parser.parse_args()
build_top = Path(getcwd())
sbox = Path(__file__).absolute().parent.parent.parent.parent.parent
assert sbox.parent.name == "sbox" and len(sbox.name) == 40
ziproot = sbox / "zip"
srcdirs = sorted(build_top.glob("art/test/*"))
srcdirs = [s for s in srcdirs if match("\d*{}-.*".format(args.shard), s.name)]
dstdirs = [copy_sources(args, ziproot, args.mode, s) for s in srcdirs]
# Use multiprocessing (i.e. forking) since tests modify their current working directory.
with Pool(cpu_count() if use_multiprocessing(args.mode) else 1) as pool:
jobs: Dict[Path, ApplyResult] = {}
for dstdir in dstdirs:
ctx = BuildTestContext(args, build_top, sbox, dstdir.name, dstdir)
jobs[dstdir] = pool.apply_async(build_test, (ctx,))
for dstdir, job in jobs.items():
try:
job.get()
except Exception as e:
raise Exception("Failed to build " + dstdir.name) from e.__cause__
# Create the final zip file which contains the content of the temporary directory.
proc = run([ZIP, "-o", args.out, "-C", ziproot, "-D", ziproot], check=True)
if __name__ == "__main__":
main()