1#!/usr/bin/env python
2#
3# Copyright (C) 2017 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"""Builds binutils."""
18from __future__ import print_function
19
20import argparse
21import logging
22import multiprocessing
23import os
24import shutil
25import site
26import subprocess
27
28
29THIS_DIR = os.path.realpath(os.path.dirname(__file__))
30site.addsitedir(os.path.join(THIS_DIR, '../../ndk'))
31
32# pylint: disable=wrong-import-position
33import ndk.abis  # pylint: disable=import-error
34import ndk.ext.shutil  # pylint: disable=import-error
35import ndk.paths  # pylint: disable=import-error
36import ndk.timer  # pylint: disable=import-error
37# pylint: enable=wrong-import-position
38
39
40def logger():
41    """Returns the module level logger."""
42    return logging.getLogger(__name__)
43
44
45def makedirs(path):
46    """os.makedirs with logging."""
47    logger().info('makedirs ' + path)
48    os.makedirs(path)
49
50
51def rmtree(path):
52    """shutil.rmtree with logging."""
53    logger().info('rmtree ' + path)
54    shutil.rmtree(path)
55
56
57def chdir(path):
58    """os.chdir with logging."""
59    logger().info('chdir ' + path)
60    os.chdir(path)
61
62
63def check_call(cmd, *args, **kwargs):
64    """subprocess.check_call with logging."""
65    logger().info('check_call %s', subprocess.list2cmdline(cmd))
66    subprocess.check_call(cmd, *args, **kwargs)
67
68
69def configure(arch, host, install_dir, src_dir):
70    """Configures binutils."""
71    is_windows = host in ('win', 'win64')
72
73    configure_host = {
74        'darwin': 'x86_64-apple-darwin',
75        'linux': 'x86_64-linux-gnu',
76        'win': 'i686-w64-mingw32',
77        'win64': 'x86_64-w64-mingw32',
78    }[host]
79
80    sysroot = ndk.paths.sysroot_path(ndk.abis.arch_to_toolchain(arch))
81    configure_args = [
82        os.path.join(src_dir, 'configure'),
83        '--target={}'.format(ndk.abis.arch_to_triple(arch)),
84        '--host={}'.format(configure_host),
85        '--enable-initfini-array',
86        '--enable-plugins',
87        '--with-sysroot={}'.format(sysroot),
88        '--prefix={}'.format(install_dir),
89    ]
90
91    if arch == 'arm64':
92        configure_args.append('--enable-fix-cortex-a53-835769')
93        configure_args.append('--enable-gold')
94    else:
95        # Gold for aarch64 currently emits broken debug info.
96        # https://issuetracker.google.com/70838247
97        configure_args.append('--enable-gold=default')
98
99    if not is_windows:
100        # Multithreaded linking is implemented with pthreads, which we
101        # historically couldn't use on Windows.
102        # TODO: Try enabling this now that we have winpthreads in mingw.
103        configure_args.append('--enable-threads')
104
105    env = {}
106
107    m32 = False
108    if host == 'darwin':
109        toolchain = ndk.paths.android_path(
110            'prebuilts/gcc/darwin-x86/host/i686-apple-darwin-4.2.1')
111        toolchain_prefix = 'i686-apple-darwin10'
112        env['MACOSX_DEPLOYMENT_TARGET'] = '10.6'
113    elif host == 'linux':
114        toolchain = ndk.paths.android_path(
115            'prebuilts/gcc/linux-x86/host/x86_64-linux-glibc2.15-4.8')
116        toolchain_prefix = 'x86_64-linux'
117    elif is_windows:
118        toolchain = ndk.paths.android_path(
119            'prebuilts/gcc/linux-x86/host/x86_64-w64-mingw32-4.8')
120        toolchain_prefix = 'x86_64-w64-mingw32'
121        if host == 'win':
122            m32 = True
123    else:
124        raise NotImplementedError
125
126    cc = os.path.join(toolchain, 'bin', '{}-gcc'.format(toolchain_prefix))
127    cxx = os.path.join(toolchain, 'bin', '{}-g++'.format(toolchain_prefix))
128
129    # Our darwin prebuilts are gcc *only*. No binutils.
130    if host == 'darwin':
131        ar = 'ar'
132        strip = 'strip'
133    else:
134        ar = os.path.join(toolchain, 'bin', '{}-ar'.format(toolchain_prefix))
135        strip = os.path.join(
136            toolchain, 'bin', '{}-strip'.format(toolchain_prefix))
137
138    env['AR'] = ar
139    env['CC'] = cc
140    env['CXX'] = cxx
141    env['STRIP'] = strip
142    if m32:
143        env['CFLAGS'] = '-m32'
144        env['CXXFLAGS'] = '-m32'
145        env['LDFLAGS'] = '-m32'
146    else:
147        env['CFLAGS'] = '-m64'
148        env['CXXFLAGS'] = '-m64'
149        env['LDFLAGS'] = '-m64'
150
151    env_args = ['env'] + ['='.join([k, v]) for k, v in env.items()]
152    check_call(env_args + configure_args)
153
154
155def build(jobs):
156    """Builds binutils."""
157    check_call(['make', '-j', str(jobs)])
158
159
160def install(jobs):
161    """Installs binutils."""
162    check_call(['make', 'install-strip', '-j', str(jobs)])
163
164
165def dist(dist_dir, base_dir, package_name):
166    """Packages binutils for distribution."""
167    has_pbzip2 = ndk.ext.shutil.which('pbzip2') is not None
168    if has_pbzip2:
169        compress_arg = '--use-compress-prog=pbzip2'
170    else:
171        compress_arg = '-j'
172
173    package_path = os.path.join(dist_dir, package_name + '.tar.bz2')
174    cmd = [
175        'tar', compress_arg, '-cf', package_path, '-C', base_dir, package_name,
176    ]
177    subprocess.check_call(cmd)
178
179
180def parse_args():
181    """Parse command line arguments."""
182    parser = argparse.ArgumentParser()
183
184    parser.add_argument(
185        '--arch', choices=ndk.abis.ALL_ARCHITECTURES, required=True)
186    parser.add_argument(
187        '--host', choices=('darwin', 'linux', 'win', 'win64'), required=True)
188
189    parser.add_argument(
190        '--clean', action='store_true',
191        help='Clean the out directory before building.')
192    parser.add_argument(
193        '-j', '--jobs', type=int, default=multiprocessing.cpu_count(),
194        help='Number of jobs to use when building.')
195
196    return parser.parse_args()
197
198
199def main():
200    """Program entry point."""
201    args = parse_args()
202    logging.basicConfig(level=logging.INFO)
203
204    total_timer = ndk.timer.Timer()
205    total_timer.start()
206
207    out_dir = ndk.paths.get_out_dir()
208    dist_dir = ndk.paths.get_dist_dir(out_dir)
209    base_build_dir = os.path.join(
210        out_dir, 'binutils', args.host, args.arch)
211    build_dir = os.path.join(base_build_dir, 'build')
212    package_name = 'binutils-{}-{}'.format(args.arch, args.host)
213    install_dir = os.path.join(base_build_dir, 'install', package_name)
214    binutils_path = os.path.join(THIS_DIR, 'binutils-2.27')
215
216    did_clean = False
217    clean_timer = ndk.timer.Timer()
218    if args.clean and os.path.exists(build_dir):
219        did_clean = True
220        with clean_timer:
221            rmtree(build_dir)
222
223    if not os.path.exists(build_dir):
224        makedirs(build_dir)
225
226    orig_dir = os.getcwd()
227    chdir(build_dir)
228    try:
229        configure_timer = ndk.timer.Timer()
230        with configure_timer:
231            configure(args.arch, args.host, install_dir, binutils_path)
232
233        build_timer = ndk.timer.Timer()
234        with build_timer:
235            build(args.jobs)
236
237        install_timer = ndk.timer.Timer()
238        with install_timer:
239            install(args.jobs)
240    finally:
241        chdir(orig_dir)
242
243    package_timer = ndk.timer.Timer()
244    with package_timer:
245        dist(dist_dir, os.path.dirname(install_dir), package_name)
246
247    total_timer.finish()
248
249    if did_clean:
250        print('Clean: {}'.format(clean_timer.duration))
251    print('Configure: {}'.format(configure_timer.duration))
252    print('Build: {}'.format(build_timer.duration))
253    print('Install: {}'.format(install_timer.duration))
254    print('Package: {}'.format(package_timer.duration))
255    print('Total: {}'.format(total_timer.duration))
256
257
258if __name__ == '__main__':
259    main()
260