1#!/usr/bin/env python2
2#
3# Copyright 2017 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6#
7# pylint: disable=cros-logging-import
8
9"""Script to build the benchmark locally with toolchain settings."""
10from __future__ import print_function
11
12import argparse
13import config
14import logging
15import os
16import subprocess
17import sys
18
19# Turn the logging level to INFO before importing other code, to avoid having
20# failed import logging messages confuse the user.
21logging.basicConfig(level=logging.INFO)
22
23
24def _parse_arguments_internal(argv):
25    parser = argparse.ArgumentParser(description='Build benchmarks with '
26                                     'specified toolchain settings')
27
28    parser.add_argument(
29        '-b',
30        '--bench',
31        required=True,
32        help='Select the benchmark to be built.')
33
34    parser.add_argument(
35        '-c',
36        '--compiler_dir',
37        metavar='DIR',
38        help='Specify the path to the compiler bin '
39        'directory.')
40
41    parser.add_argument(
42        '-o',
43        '--build_os',
44        help='Specify the host OS to build benchmark.')
45
46    parser.add_argument(
47        '-l',
48        '--llvm_prebuilts_version',
49        help='Specify the version of prebuilt LLVM.')
50
51    parser.add_argument(
52        '-f',
53        '--cflags',
54        help='Specify the optimization cflags for the toolchain.')
55
56    parser.add_argument(
57        '--ldflags',
58        help='Specify linker flags for the toolchain.')
59
60    return parser.parse_args(argv)
61
62
63# Set flags for compiling benchmarks, by changing the local
64# CFLAGS/LDFLAGS in the android makefile of each benchmark
65def set_flags(bench, cflags, ldflags):
66    if not cflags:
67        logging.info('No CFLAGS specified, using default settings.')
68        cflags = ''
69    else:
70        logging.info('Cflags setting to "%s"...', cflags)
71
72    if not ldflags:
73        logging.info('No LDFLAGS specifed, using default settings.')
74        ldflags = ''
75    else:
76        logging.info('Ldflags setting to "%s"...', ldflags)
77
78    add_flags = config.bench_flags_dict[bench]
79    add_flags(cflags, ldflags)
80    logging.info('Flags set successfully!')
81
82
83def set_build_os(build_os):
84    # Set $BUILD_OS variable for android makefile
85    if build_os:
86        os.environ['BUILD_OS'] = build_os
87        logging.info('BUILD_OS set to "%s"...', build_os)
88    else:
89        logging.info('No BUILD_OS specified, using linux as default...')
90
91
92def set_llvm_prebuilts_version(llvm_prebuilts_version):
93    # Set $LLVM_PREBUILTS_VERSION for android makefile
94    if llvm_prebuilts_version:
95        os.environ['LLVM_PREBUILTS_VERSION'] = llvm_prebuilts_version
96        logging.info('LLVM_PREBUILTS_VERSION set to "%s"...',
97                     llvm_prebuilts_version)
98    else:
99        logging.info('No LLVM_PREBUILTS_VERSION specified, '
100                     'using default one...')
101
102
103def set_compiler(compiler):
104    # If compiler_dir has been specified, copy the binaries to
105    # a temporary location, set BUILD_OS and LLVM_PREBUILTS_VERSION
106    # variables to the location
107    if compiler:
108        # Report error if path not exits
109        if not os.path.isdir(compiler):
110            logging.error('Error while setting compiler: '
111                          'Directory %s does not exist!', compiler)
112            raise OSError('Directory %s not exist.' % compiler)
113
114        # Specify temporary directory for compiler
115        tmp_dir = os.path.join(config.android_home,
116                               'prebuilts/clang/host/linux-x86', 'clang-tmp')
117
118        compiler_content = os.path.join(compiler, '.')
119
120        # Copy compiler to new directory
121        try:
122            subprocess.check_call(['cp', '-rf', compiler_content, tmp_dir])
123        except subprocess.CalledProcessError:
124            logging.error('Error while copying the compiler to '
125                          'temporary directory %s!', tmp_dir)
126            raise
127
128        # Set environment variable
129        os.environ['LLVM_PREBUILTS_VERSION'] = 'clang-tmp'
130
131        logging.info('Prebuilt Compiler set as %s.', os.path.abspath(compiler))
132
133
134def set_compiler_env(bench, compiler, build_os, llvm_prebuilts_version, cflags,
135                     ldflags):
136    logging.info('Setting compiler options for benchmark...')
137
138    # If no specific prebuilt compiler directory, use BUILD_OS and
139    # LLVM_PREBUILTS_VERSION to set the compiler version.
140    # Otherwise, use the new prebuilt compiler.
141    if not compiler:
142        set_build_os(build_os)
143        set_llvm_prebuilts_version(llvm_prebuilts_version)
144    else:
145        set_compiler(compiler)
146
147    set_flags(bench, cflags, ldflags)
148
149    return 0
150
151
152def remove_tmp_dir():
153    tmp_dir = os.path.join(config.android_home,
154                           'prebuilts/clang/host/linux-x86',
155                           'clang-tmp')
156
157    try:
158        subprocess.check_call(['rm', '-r', tmp_dir])
159    except subprocess.CalledProcessError:
160        logging.error('Error while removing the temporary '
161                      'compiler directory %s!', tmp_dir)
162        raise
163
164
165# Recover the makefile/blueprint from our patch after building
166def restore_makefile(bench):
167    pwd = os.path.join(config.android_home, config.bench_dict[bench])
168    mk_file = os.path.join(pwd, 'Android.mk')
169    if not os.path.exists(mk_file):
170        mk_file = os.path.join(pwd, 'Android.bp')
171    subprocess.check_call(['mv', os.path.join(pwd, 'tmp_makefile'), mk_file])
172
173
174# Run script to build benchmark
175def build_bench(bench, source_dir):
176    logging.info('Start building benchmark...')
177
178    raw_cmd = ('cd {android_home} '
179               '&& source build/envsetup.sh '
180               '&& lunch {product_combo} '
181               '&& mmma {source_dir} -j48'.format(
182                   android_home=config.android_home,
183                   product_combo=config.product_combo,
184                   source_dir=source_dir))
185
186    log_file = os.path.join(config.bench_suite_dir, 'build_log')
187    with open(log_file, 'a') as logfile:
188        log_head = 'Log for building benchmark: %s\n' % (bench)
189        logfile.write(log_head)
190        try:
191            subprocess.check_call(
192                ['bash', '-c', raw_cmd], stdout=logfile, stderr=logfile)
193        except subprocess.CalledProcessError:
194            logging.error('Error while running %s, please check '
195                          '%s for more info.', raw_cmd, log_file)
196            restore_makefile(bench)
197            raise
198
199    logging.info('Logs for building benchmark %s are written to %s.',
200                 bench, log_file)
201    logging.info('Benchmark built successfully!')
202
203
204def main(argv):
205    arguments = _parse_arguments_internal(argv)
206
207    bench = arguments.bench
208    compiler = arguments.compiler_dir
209    build_os = arguments.build_os
210    llvm_version = arguments.llvm_prebuilts_version
211    cflags = arguments.cflags
212    ldflags = arguments.ldflags
213
214    try:
215        source_dir = config.bench_dict[bench]
216    except KeyError:
217        logging.error('Please select one benchmark from the list below:\n\t' +
218                      '\n\t'.join(config.bench_list))
219        raise
220
221    set_compiler_env(bench, compiler, build_os, llvm_version, cflags, ldflags)
222
223    build_bench(bench, source_dir)
224
225    # If flags has been set, remember to restore the makefile/blueprint to
226    # original ones.
227    restore_makefile(bench)
228
229    # If a tmp directory is used for compiler path, remove it after building.
230    if compiler:
231        remove_tmp_dir()
232
233
234if __name__ == '__main__':
235    main(sys.argv[1:])
236