1#!/bin/sh
2#
3# Copyright (C) 2018 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17""":" # Shell script (in docstring to appease pylint)
18# Find and invoke hermetic python3 interpreter
19. "`dirname $0`/envsetup.sh"; exec "$PY3" "$0" "$@"
20# Shell script end
21
22Invoke trusty build system and run tests.
23"""
24
25import argparse
26import getpass
27import json
28import multiprocessing
29import os
30import pathlib
31import re
32import shutil
33import stat
34import subprocess
35import sys
36from zipfile import ZipFile, ZipInfo, ZIP_DEFLATED
37
38import run_tests
39import trusty_build_config
40from trusty_build_config import (
41    TrustyAndroidTest,
42    TrustyBuildConfig,
43    TrustyPortTest,
44    TrustyCompositeTest,
45)
46
47from log_processor import LogEngine
48
49TRUSTED_APP_MAKEFILE_PATH = "trusty/user/base/make/trusted_app.mk"
50TRUSTED_LOADABLE_APP_MAKEFILE_PATH = "trusty/kernel/make/loadable_app.mk"
51GEN_MANIFEST_MAKEFILE_PATH = "trusty/user/base/make/gen_manifest.mk"
52
53ZIP_CREATE_SYSTEM_UNIX = 3
54SYMLINK_MODE = stat.S_IFLNK | stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO
55
56
57def get_new_build_id(build_root):
58    """Increment build-id file and return new build-id number."""
59    path = os.path.join(build_root, "BUILDID")
60    try:
61        with open(path, "r", encoding="utf-8") as f:
62            num = int(f.read()) + 1
63    except IOError:
64        num = 1
65    with open(path, "w", encoding="utf-8") as f:
66        f.write(str(num))
67        f.truncate()
68        # Return buildid string: <user>@<hostname>-<num>
69        # Use getpass.getuser() to avoid non-portability/failure of
70        # os.getlogin()
71        return getpass.getuser() + "@" + os.uname()[1] + "-" + str(num)
72
73
74def mkdir(path):
75    """Create directory including parents if it does not already exist."""
76    try:
77        os.makedirs(path)
78    except OSError:
79        if not os.path.isdir(path):
80            raise
81
82
83def copy_file(src, dest, optional=False):
84    """Copy a file.
85
86    Copy a file or exit if the file cannot be copied.
87
88    Args:
89       src: Path of file to copy.
90       dest: Path to copy file to.
91       optional: Optional boolean argument. If True don't exit if source file
92           does not exist.
93    """
94    if not os.path.exists(src) and optional:
95        return
96    print("Copy:", repr(src), "->", repr(dest))
97    shutil.copy(src, dest)
98
99
100def archive_build_file(args, project, src, dest=None, optional=False):
101    """Copy a file to build archive directory.
102
103    Construct src and dest path and call copy_file.
104
105    Args:
106       args: Program arguments.
107       project: Project name.
108       src: Source path relative to project build dir.
109       dest: Optional dest path relative to archive dir. Can be omitted if src
110           is a simple filename.
111       optional: Optional boolean argument. If True don't exit if source file
112           does not exist.
113    """
114    if not dest:
115        dest = src
116    src = os.path.join(args.build_root, "build-" + project, src)
117    # dest must be a fixed path for repeated builds of the same artifact
118    # for compatibility with prebuilt update scripts.
119    # Project is fine because that specifies what artifact is being looked
120    # for - LK for a specific target.
121    # BUILD_ID or feature selections that may change are not, because the
122    # prebuilt update script cannot predict the path at which the artifact
123    # will live.
124    dest = os.path.join(args.archive, project + "." + dest)
125    copy_file(src, dest, optional=optional)
126
127
128def archive_symlink(zip_archive, arcname, target):
129    """Add a symbolic link to the archive
130
131    Args:
132       zip_archive: Archive to update
133       arcname: Filename in the archive to be added
134       target: Symbolic link target
135    """
136    zinfo = ZipInfo(arcname)
137    zinfo.create_system = ZIP_CREATE_SYSTEM_UNIX
138    zinfo.external_attr = SYMLINK_MODE << 16
139    zip_archive.writestr(zinfo, target)
140
141
142def is_child_of_any(path, possible_parents):
143    for possible_parent in possible_parents:
144        if path.startswith(possible_parent):
145            return True
146    return False
147
148
149def archive_dir(zip_archive, src, dest, omit=()):
150    """Recursively add a directory to a ZIP file.
151
152    Recursively add the src directory to the ZIP with dest path inside the
153    archive.
154
155    Args:
156       zip_archive: A ZipFile opened for append or write.
157       src: Source directory to add to the archive.
158       dest: Destination path inside the archive (must be a relative path).
159       omit: List of directorys to omit from the archive. Specified as relative
160           paths from `src`.
161    """
162    for root, dirs, files in os.walk(src):
163        rel_root = os.path.relpath(root, start=src)
164        if is_child_of_any(rel_root, omit):
165            continue
166
167        for d in dirs:
168            dir_path = os.path.join(root, d)
169
170            if os.path.islink(dir_path):
171                archive_dest = os.path.join(
172                    dest, os.path.relpath(dir_path, start=src)
173                )
174                archive_symlink(
175                    zip_archive, archive_dest, os.readlink(dir_path)
176                )
177
178        for f in files:
179            file_path = os.path.join(root, f)
180            archive_dest = os.path.join(
181                dest, os.path.relpath(file_path, start=src)
182            )
183            if os.path.islink(file_path):
184                archive_symlink(
185                    zip_archive, archive_dest, os.readlink(file_path)
186                )
187            else:
188                zip_archive.write(file_path, archive_dest)
189
190
191def archive_file(zip_archive, src_file, dest_dir="", optional=False):
192    """Add a file to a ZIP file.
193
194    Adds src_file to archive in the directory dest_dir, relative to the root of
195    the archive.
196
197    Args:
198       zip_archive: A ZipFile opened for append or write.
199       src_file: Source file to add to the archive.
200       dest_dir: Relative destination path in the archive for this file.
201       optional: Optional boolean argument. If True don't exit if source file
202           does not exist.
203    """
204    if not os.path.exists(src_file) and optional:
205        return
206    zip_archive.write(
207        src_file, os.path.join(dest_dir, os.path.basename(src_file))
208    )
209
210
211def assemble_sdk(build_config, args):
212    """Assemble Trusty SDK archive"""
213    filename = os.path.join(args.archive, "trusty_sdk-" + args.buildid + ".zip")
214    with ZipFile(filename, "a", compression=ZIP_DEFLATED) as sdk_archive:
215        print("Building SDK archive ZIP...")
216        for project in args.project:
217            print(f"Adding SDK project... ({project})")
218            project_buildroot = os.path.join(
219                args.build_root, "build-" + project
220            )
221
222            project_sysroot_dir = os.path.join("sysroots", project, "usr")
223            src = os.path.join(project_buildroot, "sdk", "sysroot", "usr")
224            archive_dir(sdk_archive, src, project_sysroot_dir, omit=["lib/doc"])
225
226            src = os.path.join(project_buildroot, "sdk", "LICENSE")
227            archive_file(sdk_archive, src)
228
229            project_makefile_dir = os.path.join("make", project)
230            src = os.path.join(project_buildroot, "sdk", "make")
231            archive_dir(sdk_archive, src, project_makefile_dir)
232
233            project_tools_dir = os.path.join("sysroots", project, "tools")
234            src = os.path.join(
235                project_buildroot, "host_tools", "apploader_package_tool"
236            )
237            archive_file(sdk_archive, src, project_tools_dir, optional=True)
238
239            src = os.path.join(
240                project_buildroot, "sdk", "tools", "manifest_compiler.py"
241            )
242            archive_file(sdk_archive, src, project_tools_dir)
243
244            project_keys = build_config.signing_keys(project)
245            for filename in project_keys:
246                archive_file(sdk_archive, filename, project_tools_dir)
247
248        print("Adding SDK sundries...")
249
250        # Copy the app makefile
251        archive_file(sdk_archive, TRUSTED_APP_MAKEFILE_PATH, "make")
252        archive_file(sdk_archive, TRUSTED_LOADABLE_APP_MAKEFILE_PATH, "make")
253        archive_file(sdk_archive, GEN_MANIFEST_MAKEFILE_PATH, "make")
254
255        # Copy doc files
256        for doc_file in build_config.doc_files:
257            archive_file(sdk_archive, doc_file)
258
259        # Add clang version info
260        envsetup = os.path.join(args.script_dir, "envsetup.sh")
261        cmd = f"source {envsetup} && echo $CLANG_BINDIR"
262        clang_bindir = (
263            subprocess.check_output(cmd, shell=True, executable="/bin/bash")
264            .decode()
265            .strip()
266        )
267        clang_dir = os.path.join(clang_bindir, "../")
268
269        cmd = f"cd {clang_dir}; git rev-parse HEAD"
270        clang_prebuilt_commit = (
271            subprocess.check_output(cmd, shell=True, executable="/bin/bash")
272            .decode()
273            .strip()
274        )
275
276        archive_file(
277            sdk_archive,
278            os.path.join(clang_dir, "AndroidVersion.txt"),
279            "clang-version",
280        )
281        archive_file(
282            sdk_archive,
283            os.path.join(clang_dir, "clang_source_info.md"),
284            "clang-version",
285        )
286        sdk_archive.writestr(
287            os.path.join("clang-version", "PrebuiltCommitId.txt"),
288            clang_prebuilt_commit,
289        )
290
291        # Add trusty version info
292        sdk_archive.writestr("Version.txt", args.buildid)
293
294        # Add the toolchain if requested
295        if args.archive_toolchain:
296            _head, clang_ver = os.path.split(os.path.realpath(clang_dir))
297            print(f"Adding SDK toolchain... ({clang_ver})")
298            archive_dir(
299                sdk_archive, clang_dir, os.path.join("toolchain", clang_ver)
300            )
301            archive_symlink(
302                sdk_archive, os.path.join("toolchain", "clang"), clang_ver
303            )
304
305
306def build(args):
307    """Call build system and copy build files to archive dir."""
308    mkdir(args.build_root)
309
310    if args.buildid is None:
311        args.buildid = get_new_build_id(args.build_root)
312    print("BuildID", args.buildid)
313
314    nice = "" if args.no_nice else "nice"
315
316    # build projects
317    failed = []
318
319    for project in args.project:
320        cmd = (
321            f"export BUILDROOT={args.build_root};"
322            f"export BUILDID={args.buildid};"
323            f"{nice} $BUILDTOOLS_BINDIR/make {project} "
324            f"-f $LKROOT/makefile -j {args.jobs}"
325        )
326        # Call envsetup.  If it fails, abort.
327        envsetup = os.path.join(args.script_dir, "envsetup.sh")
328        cmd = f"source {envsetup:s} && ({cmd:s})"
329
330        # check if we are attached to a real terminal
331        terminal_output = sys.stdout.isatty()
332
333        if args.color_log and terminal_output:
334            # postprocess output with custom log processor
335
336            # define additional env variable for make to generate log markers
337            cmd = f"export LOG_POSTPROCESSING=1; {cmd:s}"
338
339            with (
340                open(project + ".log", "wt", encoding="utf-8") as log_file,
341                LogEngine(log_file) as log_engine,
342            ):
343                status = subprocess.call(
344                    cmd,
345                    shell=True,
346                    executable="/bin/bash",
347                    stdout=log_engine.stdout,
348                    stderr=log_engine.stderr,
349                )
350        else:  # no output intercepting
351            status = subprocess.call(cmd, shell=True, executable="/bin/bash")
352
353        print("cmd: '" + cmd + "' returned", status)
354        if status:
355            failed.append(project)
356
357    if failed:
358        print()
359        print("some projects have failed to build:")
360        print(str(failed))
361        sys.exit(1)
362
363
364def zip_dir(zip_archive, src, dest, filterfunc=lambda _: True):
365    """Recursively add a directory to a ZIP file.
366
367    Recursively add the src directory to the ZIP with dest path inside the
368    archive.
369
370    Args:
371       zip_archive: A ZipFile opened for append or write.
372       src: Source directory to add to the archive.
373       dest: Destination path inside the archive (must be a relative path).
374    """
375    for root, _dirs, files in os.walk(src):
376        for f in files:
377            if not filterfunc(f):
378                continue
379            file_path = os.path.join(root, f)
380            archive_dest = os.path.join(
381                dest, os.path.relpath(file_path, start=src)
382            )
383            zip_archive.write(file_path, archive_dest)
384
385
386def zip_file(zip_archive, src_file, dest_dir=""):
387    """Add a file to a ZIP file.
388
389    Adds src_file to archive in the directory dest_dir, relative to the root of
390    the archive.
391
392    Args:
393       zip_archive: A ZipFile opened for append or write.
394       src_file: Source file to add to the archive.
395       dest_dir: Relative destination path in the archive for this file.
396    """
397    zip_archive.write(
398        src_file, os.path.join(dest_dir, os.path.basename(src_file))
399    )
400
401
402def archive_symbols(args, project):
403    """Archive symbol files for the kernel and each trusted app"""
404    proj_buildroot = os.path.join(args.build_root, "build-" + project)
405    filename = os.path.join(args.archive, f"{project}-{args.buildid}.syms.zip")
406
407    with ZipFile(filename, "a", compression=ZIP_DEFLATED) as zip_archive:
408        print("Archiving symbols in " + os.path.relpath(filename, args.archive))
409
410        # archive the kernel elf file
411        zip_file(zip_archive, os.path.join(proj_buildroot, "lk.elf"))
412
413        # archive the kernel symbols
414        zip_file(zip_archive, os.path.join(proj_buildroot, "lk.elf.sym"))
415        zip_file(zip_archive, os.path.join(proj_buildroot, "lk.elf.sym.sorted"))
416
417        # archive path/to/app.syms.elf for each trusted app
418        zip_dir(
419            zip_archive, proj_buildroot, "", lambda f: f.endswith("syms.elf")
420        )
421
422
423def archive_listings(args, project):
424    """Archive lst files for the kernel and each trusted app"""
425    proj_buildroot = os.path.join(args.build_root, "build-" + project)
426    filename = os.path.join(args.archive, f"{project}-{args.buildid}.lst.zip")
427
428    with ZipFile(filename, "a", compression=ZIP_DEFLATED) as zip_archive:
429        print("Archiving .lst in " + os.path.relpath(filename, args.archive))
430
431        # archive all .lst files under the buildroot
432        zip_dir(
433            zip_archive, proj_buildroot, "", lambda f: f.endswith(".lst")
434        )
435
436
437def create_uuid_map(args, project):
438    """Creating a mapping txt file for uuid and symbol files"""
439
440    def time_from_bytes(f, n: int) -> str:
441        """Read n bytes from f as an int, and convert that int to a string."""
442        rtime = int.from_bytes(f.read(n), byteorder="little")
443        width = 2 * n
444        return f"{rtime:0{width}x}"
445
446    proj_buildroot = os.path.join(args.build_root, "build-" + project)
447    uuidmapfile = os.path.join(args.archive, "uuid-map.txt")
448    zipfile = os.path.join(args.archive, f"{project}-{args.buildid}.syms.zip")
449    sym_files = list(pathlib.Path(proj_buildroot).rglob("*.syms.elf"))
450
451    for file in sym_files:
452        folder = file.parents[0]
453        manifest_files = list(pathlib.Path(folder).glob("*.manifest"))
454        if len(manifest_files) == 1:
455            manifest = manifest_files[0]
456            with open(manifest, "rb") as f:
457                time_low = time_from_bytes(f, 4)
458                time_mid = time_from_bytes(f, 2)
459                time_hi_and_version = time_from_bytes(f, 2)
460                clock_seq_and_node = [time_from_bytes(f, 1) for _ in range(8)]
461                uuid_str = (
462                    f"{time_low}-{time_mid}-{time_hi_and_version}-"
463                    f"{clock_seq_and_node[0]}{clock_seq_and_node[1]}-"
464                    f"{clock_seq_and_node[2]}{clock_seq_and_node[3]}"
465                    f"{clock_seq_and_node[4]}{clock_seq_and_node[5]}"
466                    f"{clock_seq_and_node[6]}{clock_seq_and_node[7]}"
467                )
468            with open(uuidmapfile, "a", encoding="utf-8") as f:
469                f.write(f"{uuid_str}, {file.relative_to(proj_buildroot)}\n")
470
471    if os.path.exists(uuidmapfile):
472        with ZipFile(zipfile, "a", compression=ZIP_DEFLATED) as zip_archive:
473            zip_file(zip_archive, uuidmapfile)
474        os.remove(uuidmapfile)
475
476
477def create_scripts_archive(args, project):
478    """Create an archive for the scripts"""
479    coverage_script = os.path.join(args.script_dir, "genReport.py")
480    scripts_zip = os.path.join(
481        args.archive, f"{project}-{args.buildid}.scripts.zip"
482    )
483    if not os.path.exists(coverage_script):
484        print("Coverage script does not exist!")
485        return
486
487    with ZipFile(scripts_zip, "a", compression=ZIP_DEFLATED) as zip_archive:
488        zip_file(zip_archive, coverage_script)
489
490
491def archive(build_config, args):
492    if args.archive is None:
493        return
494
495    mkdir(args.archive)
496
497    # Copy the files we care about to the archive directory
498    for project in args.project:
499        # config-driven archiving
500        for item in build_config.dist:
501            archive_build_file(
502                args, project, item.src, item.dest, optional=item.optional
503            )
504
505        # copy out tos.img if it exists
506        archive_build_file(args, project, "tos.img", optional=True)
507
508        # copy out monitor if it exists
509        archive_build_file(
510            args, project, "monitor/monitor.bin", "monitor.bin", optional=True
511        )
512
513        # copy out trusty.padded if it exists
514        archive_build_file(args, project, "trusty.padded", optional=True)
515
516        # copy out trusty.signed if it exists
517        archive_build_file(args, project, "trusty.signed", optional=True)
518
519        # copy out trusty_usb.signed if it exists
520        archive_build_file(args, project, "trusty_usb.signed", optional=True)
521
522        # copy out lk image
523        archive_build_file(args, project, "lk.bin")
524        archive_build_file(args, project, "lk.elf")
525
526        # copy out qemu package if it exists
527        archive_build_file(
528            args, project, "trusty_qemu_package.zip", optional=True
529        )
530
531        # copy out emulator image package if it exists
532        archive_build_file(
533            args, project, "trusty_image_package.tar.gz", optional=True
534        )
535
536        # copy out test package if it exists
537        archive_build_file(
538            args, project, "trusty_test_package.zip", optional=True
539        )
540
541        # export the app package tool for use in the SDK. This can go away once
542        # all the SDK patches have landed, as the tool will be packaged in the
543        # SDK zip.
544        archive_build_file(
545            args,
546            project,
547            "host_tools/apploader_package_tool",
548            "apploader_package_tool",
549            optional=True,
550        )
551
552        # copy out symbol files for kernel and apps
553        archive_symbols(args, project)
554
555        # copy out listings files for kernel and apps
556        archive_listings(args, project)
557
558        # create map between UUID and symbolic files
559        create_uuid_map(args, project)
560
561        # create zip file containing scripts
562        create_scripts_archive(args, project)
563
564    # create sdk zip
565    assemble_sdk(build_config, args)
566
567
568def get_build_deps(project_name, project, project_names, already_built):
569    if project_name not in already_built:
570        already_built.add(project_name)
571        for dep_project_name, dep_project in project.also_build.items():
572            get_build_deps(
573                dep_project_name, dep_project, project_names, already_built
574            )
575        project_names.append(project_name)
576
577
578def create_test_map(args, build_config, projects):
579    for project_name in projects:
580        test_map = {}
581        test_map["port_tests"] = []
582        test_map["commands"] = []
583        test_names = set()
584        duplicates = set()
585        project = build_config.get_project(project_name)
586
587        if not project or not project.tests:
588            return
589
590        port_test_prefix = "android-port-test:"
591        project_type_prefix = re.compile("([^:]+:)+")
592
593        for test in project.tests:
594            test_type = None
595            match test:
596                case TrustyCompositeTest() if any(
597                    s
598                    for s in test.sequence
599                    if s.name.startswith(port_test_prefix)
600                ):
601                    test_type = TrustyCompositeTest
602                case TrustyAndroidTest() if test.name.startswith(
603                    port_test_prefix
604                ):
605                    test_type = TrustyPortTest
606                case TrustyAndroidTest():
607                    test_type = TrustyAndroidTest
608                case _:
609                    pass
610
611            if test_type:
612                test_obj = {"needs": []}
613                test_name = re.sub(project_type_prefix, "", test.name)
614
615                if test_name in test_names:
616                    duplicates.add(test_name)
617                    continue
618                test_names.add(test_name)
619
620                if hasattr(test, "need") and hasattr(test.need, "flags"):
621                    test_obj["needs"] = list(test.need.flags)
622                if hasattr(test, "port_type"):
623                    test_obj["type"] = str(test.port_type)
624
625                match test_type:
626                    case trusty_build_config.TrustyPortTest:
627                        test_obj["port_name"] = test_name
628                        test_map["port_tests"].append(test_obj)
629                    case trusty_build_config.TrustyAndroidTest:
630                        test_obj["command_name"] = test_name
631                        test_obj["command"] = test.command
632                        test_map["commands"].append(test_obj)
633                    case trusty_build_config.TrustyCompositeTest:
634                        test_obj["port_name"] = test_name
635                        test_obj["sequence"] = []
636
637                        for subtest in test.sequence:
638                            subtest_name = re.sub(
639                                project_type_prefix, "", subtest.name
640                            )
641                            test_obj["sequence"].append(subtest_name)
642                            if hasattr(subtest, "need") and hasattr(
643                                subtest.need, "flags"
644                            ):
645                                test_obj["needs"] += list(subtest.need.flags)
646
647                            test_obj["needs"] += list(set(test_obj["needs"]))
648
649                        test_map["port_tests"].append(test_obj)
650
651        if duplicates:
652            print("ERROR: The following port tests are included multiple times")
653            for port in duplicates:
654                print(port)
655            sys.exit(-1)
656
657        project_buildroot = os.path.join(
658            args.build_root, "build-" + project_name
659        )
660        zip_path = os.path.join(project_buildroot, "trusty_test_package.zip")
661        with ZipFile(zip_path, "a", compression=ZIP_DEFLATED) as zipf:
662            zipf.writestr(
663                project_name + "-test-map.json", json.dumps(test_map, indent=4)
664            )
665
666
667def main(default_config=None, emulator=True):
668    parser = argparse.ArgumentParser()
669
670    parser.add_argument(
671        "project",
672        type=str,
673        nargs="*",
674        default=[".test.all"],
675        help="Project to build and/or test.",
676    )
677    parser.add_argument(
678        "--build-root",
679        type=os.path.abspath,
680        default=None,
681        help="Root of intermediate build directory.",
682    )
683    parser.add_argument(
684        "--archive",
685        type=str,
686        default=None,
687        help="Location of build artifacts directory. If "
688        "omitted, no artifacts will be produced.",
689    )
690    parser.add_argument(
691        "--archive-toolchain",
692        action="store_true",
693        help="Include the clang toolchain in the archive.",
694    )
695    parser.add_argument("--buildid", type=str, help="Server build id")
696    parser.add_argument(
697        "--jobs",
698        type=str,
699        default=multiprocessing.cpu_count(),
700        help="Max number of build jobs.",
701    )
702    parser.add_argument(
703        "--test",
704        type=str,
705        action="append",
706        help="Manually specify test(s) to run. "
707        "Only build projects that have test(s) enabled that "
708        "matches a listed regex.",
709    )
710    parser.add_argument(
711        "--verbose",
712        action="store_true",
713        help="Verbose debug output from test(s).",
714    )
715    parser.add_argument(
716        "--debug-on-error",
717        action="store_true",
718        help="Wait for debugger connection if test fails.",
719    )
720    parser.add_argument(
721        "--clang", action="store_true", default=None, help="Build with clang."
722    )
723    parser.add_argument("--skip-build", action="store_true", help="Skip build.")
724    parser.add_argument(
725        "--skip-tests", action="store_true", help="Skip running tests."
726    )
727    parser.add_argument(
728        "--run-disabled-tests",
729        action="store_true",
730        help="Also run disabled tests.",
731    )
732    parser.add_argument(
733        "--skip-project",
734        action="append",
735        default=[],
736        help="Remove project from projects being built.",
737    )
738    parser.add_argument(
739        "--config",
740        type=str,
741        help="Path to an alternate " "build-config file.",
742        default=default_config,
743    )
744    parser.add_argument(
745        "--android",
746        type=str,
747        help="Path to an Android build to run tests against.",
748    )
749    parser.add_argument(
750        "--color-log",
751        action="store_true",
752        help="Use colored build logs with pinned status lines.",
753    )
754    parser.add_argument(
755        "--no-nice",
756        action="store_true",
757        help="Do not use nice to run the build.",
758    )
759    parser.add_argument(
760        "--script-dir",
761        type=os.path.abspath,
762        default=os.path.dirname(os.path.abspath(__file__)),
763        help="Override the path to the directory of the script. This is for a "
764             "workaround to support the Soong-built binary."
765    )
766    args = parser.parse_args()
767
768
769    # Change the current directory to the Trusty root
770    # We do this after parsing all the arguments because
771    # some of the paths, e.g., script-dir, might be relative
772    # to the directory that the script was called from, not
773    # to the Trusty root directory
774    top = os.path.abspath(os.path.join(args.script_dir, "../../../../.."))
775    os.chdir(top)
776
777    if not args.build_root:
778        args.build_root = os.path.join(top, "build-root")
779
780    # Depending on trusty_build_config.py's default config path doesn't work on
781    # the Soong-built python binary.
782    config_file = args.config
783    if not config_file:
784        config_file = os.path.join(args.script_dir, "build-config")
785
786    build_config = TrustyBuildConfig(
787        config_file=config_file, android=args.android
788    )
789
790    projects = []
791    for project in args.project:
792        if project == ".test.all":
793            projects += build_config.get_projects(build=True)
794        elif project == ".test":
795            projects += build_config.get_projects(build=True, have_tests=True)
796        else:
797            projects.append(project)
798
799    # skip specific projects
800    ok = True
801    for skip in args.skip_project:
802        if skip in projects:
803            projects.remove(skip)
804        else:
805            sys.stderr.write(f"ERROR unknown project --skip-project={skip}\n")
806            ok = False
807    if not ok:
808        sys.exit(1)
809
810    # If there's any test filters, ignore projects that don't have
811    # any tests that match those filters.
812    test_filters = (
813        [re.compile(test) for test in args.test] if args.test else None
814    )
815    if test_filters:
816        projects = run_tests.projects_to_test(
817            build_config,
818            projects,
819            test_filters,
820            run_disabled_tests=args.run_disabled_tests,
821        )
822
823    # find build dependencies
824    projects_old = projects
825    projects = []
826    built_projects = set()
827    for project_name in projects_old:
828        get_build_deps(
829            project_name,
830            build_config.get_project(project_name),
831            projects,
832            built_projects,
833        )
834    args.project = projects
835
836    print("Projects", str(projects))
837
838    if args.skip_build:
839        print("Skip build for", args.project)
840    else:
841        build(args)
842        create_test_map(args, build_config, projects)
843        archive(build_config, args)
844
845    # Run tests
846    if not args.skip_tests:
847        test_result = run_tests.test_projects(
848            build_config,
849            args.build_root,
850            projects,
851            qemu_instance_id=None,
852            run_disabled_tests=args.run_disabled_tests,
853            test_filters=test_filters,
854            verbose=args.verbose,
855            debug_on_error=args.debug_on_error,
856            emulator=emulator,
857        )
858
859        test_result.print_results()
860        if test_result.failed_projects:
861            sys.exit(1)
862
863
864if __name__ == "__main__":
865    main()
866