scripts: Add build script

Add script to invoke trusty build system and run tests.

Bug: 117282103
Change-Id: I462e4b08d9427360728821d842f740a9b8f50c15
diff --git a/scripts/build.py b/scripts/build.py
new file mode 100755
index 0000000..e77ec55
--- /dev/null
+++ b/scripts/build.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python2.7
+#
+# Copyright (C) 2018 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.
+#
+"""Invoke trusty build system and run tests."""
+
+import argparse
+import multiprocessing
+import os
+import shutil
+import subprocess
+import sys
+
+import run_tests
+import trusty_build_config
+
+script_dir = os.path.dirname(os.path.abspath(__file__))
+
+
+def get_new_build_id(build_root):
+    """Increment build-id file and return new build-id number."""
+    path = os.path.join(build_root, "BUILDID")
+    try:
+        with open(path, "r") as f:
+            num = int(f.read()) + 1
+    except IOError:
+        num = 1
+    with open(path, "w") as f:
+        f.write(str(num))
+        f.truncate()
+        # Return buildid string: <user>@<hostname>-<num>
+        return os.getlogin() + "@" + os.uname()[1] + "-" + str(num)
+
+
+def mkdir(path):
+    """Create directory includig parents if it does not already exist."""
+    try:
+        os.makedirs(path)
+    except OSError:
+        if not os.path.isdir(path):
+            raise
+
+
+def copy_file(src, dest, optional=False):
+    """Copy a file.
+
+    Copy a file or exit if the file cannot be copied.
+
+    Args:
+       src: Path of file to copy.
+       dest: Path to copy file to.
+       optional: Optional boolean argument. If True don't exit if source file
+           does not exist.
+    """
+    if not os.path.exists(src) and optional:
+        return
+    print "Copy:", repr(src), "->", repr(dest)
+    shutil.copy(src, dest)
+
+
+def archive_build_file(args, project, src, dest=None, optional=False):
+    """Copy a file to build archive directory.
+
+    Construct src and dest path and call copy_file.
+
+    Args:
+       args: Program arguments.
+       project: Project name.
+       src: Source path relative to project build dir.
+       dest: Optional dest path relative to archive dir. Can be ommitted if src
+           is a simple filename.
+       optional: Optional boolean argument. If True don't exit if source file
+           does not exist.
+    """
+    if not dest:
+        dest = src
+    src = os.path.join(args.build_root, "build-" + project, src)
+    dest = os.path.join(args.archive, project + "-" + args.buildid + "." + dest)
+    copy_file(src, dest, optional=optional)
+
+
+def build(args):
+    """Call build system and copy build files to archive dir."""
+    mkdir(args.build_root)
+    mkdir(args.archive)
+
+    # build projects
+    failed = []
+
+    for project in args.project:
+        cmd = "source " + os.path.join(script_dir, "envsetup.sh")
+        cmd += "; export BUILDROOT=" + args.build_root
+        cmd += "; export BUILDID=" + args.buildid
+        cmd += "; nice make " + project + " -j " + str(args.jobs)
+        status = subprocess.call(cmd, shell=True, executable="/bin/bash")
+        print "cmd: '" + cmd + "' returned", status
+        if status:
+            failed.append(project)
+
+    if failed:
+        print
+        print "some projects have failed to build:"
+        print str(failed)
+        exit(1)
+
+    # Copy the files we care about to the archive directory
+    for project in args.project:
+        # copy out tos.img if it exists
+        archive_build_file(args, project, "tos.img", optional=True)
+
+        # copy out monitor if it exists
+        archive_build_file(args, project, "monitor/monitor.bin", "monitor.bin",
+                           optional=True)
+
+        # copy out lk image
+        archive_build_file(args, project, "lk.bin")
+
+        # collect and save all .lst
+        subprocess.call("cd " +
+                        os.path.join(args.build_root, "build-" + project) +
+                        ';find . -name "*.lst" -print ' +
+                        "| zip " + os.path.join(args.archive, project + "-" +
+                                                args.buildid + ".lst.zip") +
+                        " -@", shell=True, executable="/bin/bash")
+
+
+def main():
+    top = os.path.abspath(os.path.join(script_dir, "../../../../.."))
+    os.chdir(top)
+
+    parser = argparse.ArgumentParser()
+
+    parser.add_argument("project", type=str, nargs="*", default=[".test.all"],
+                        help="Project to build and/or test.")
+    parser.add_argument("--build-root", type=str,
+                        default=os.path.join(top, "build-root"),
+                        help="Root of intermediate build directory.")
+    parser.add_argument("--archive", type=str, default=None,
+                        help="Location of build results directory.")
+    parser.add_argument("--buildid", type=str, help="Server build id")
+    parser.add_argument("--jobs", type=str, default=multiprocessing.cpu_count(),
+                        help="Max number of build jobs.")
+    parser.add_argument("--skip-build", action="store_true", help="Skip build.")
+    parser.add_argument("--skip-tests", action="store_true",
+                        help="Skip running tests.")
+    args = parser.parse_args()
+
+    if args.archive is None:
+        args.archive = os.path.join(args.build_root, "archive")
+
+    if args.buildid is None:
+        args.buildid = get_new_build_id(args.build_root)
+    print "BuildID", args.buildid
+
+    build_config = trusty_build_config.TrustyBuildConfig()
+
+    projects = []
+    for project in args.project:
+        if project == ".test.all":
+            projects += build_config.get_projects(build=True)
+        elif project == ".test":
+            projects += build_config.get_projects(build=True, have_tests=True)
+        else:
+            projects.append(project)
+    args.project = projects
+    print "Projects", str(projects)
+
+    if args.skip_build:
+        print "Skip build for", args.project
+    else:
+        build(args)
+
+    # Run tests
+    if not args.skip_tests:
+        test_failed = []
+        test_results = []
+
+        for project in projects:
+            test_result = run_tests.run_tests(build_config, args.build_root,
+                                              project)
+            if not test_result.passed:
+                test_failed.append(project)
+            test_results.append(test_result)
+
+        for test_result in test_results:
+            test_result.print_results()
+
+        if test_failed:
+            sys.stdout.flush()
+            sys.stderr.write("\n")
+            sys.stderr.write(str(len(test_failed)) +
+                             " projects have failed tests:\n")
+            sys.stderr.write(str(test_failed) + "\n")
+            exit(1)
+
+
+if __name__ == "__main__":
+    main()