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